├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── deps.edn ├── slim └── deps.edn └── src └── org └── corfield └── build.clj /.gitignore: -------------------------------------------------------------------------------- 1 | .calva/output-window/ 2 | .classpath 3 | .clj-kondo/.cache 4 | .cpcache 5 | .eastwood 6 | .factorypath 7 | .hg/ 8 | .hgignore 9 | .java-version 10 | .lein-* 11 | .lsp/.cache 12 | .lsp/sqlite.db 13 | .nrepl-history 14 | .nrepl-port 15 | .portal 16 | .project 17 | .rebel_readline_history 18 | .settings 19 | .socket-repl-port 20 | .sw* 21 | .vscode 22 | *.class 23 | *.jar 24 | *.swp 25 | *~ 26 | /checkouts 27 | /classes 28 | /target 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | * v0.9.2 9c9f078 -- 2023-01-17 4 | * Update `tools.build` to v0.9.2 (for TBUILD-30 fix). 5 | 6 | * v0.9.1 831c70f -- 2023-01-14 7 | * Update `tools.build` to v0.9.1. 8 | 9 | * v0.9.0 d758807 -- 2022-12-22 10 | * Update `tools.build` to v0.9.0 (and remove the workaround added to `build-clj` in v0.8.5 since [TBUILD-34](https://clojure.atlassian.net/browse/TBUILD-34) has been fixed). 11 | * Update to non-alpha `tools.deps`. _[Note: `build-clj` uses `deps-deploy` which still depends on `tools.deps.alpha`.]_ 12 | 13 | * v0.8.5 de693d0 -- 2022-11-29 14 | * Modify calls to `compile-clj` and `java-command` to default `:java-cmd` based on the `JAVA_CMD` and/or `JAVA_HOME` environment variables, if set. This won't be necessary when [TBUILD-34](https://clojure.atlassian.net/browse/TBUILD-34) is addressed. _[This workaround has been removed in v0.9.0.]_ 15 | * Update `tools.build` to v0.8.5. 16 | 17 | * v0.8.3 7ac1f8d -- 2022-06-28 18 | * Fix [#23](https://github.com/seancorfield/build-clj/issues/23) by propagating `:exclusions` in `lifted-basis` (for `:transitive true`). 19 | * Update `tools.build` to v0.8.3 for `data_readers` fixes. 20 | 21 | * v0.8.2 0ffdb4c -- 2022-06-09 22 | * Update `tools.build` to v0.8.2 for `compile-clj` enhancement and dependency updates. 23 | 24 | * v0.8.0 9bd8b8a -- 2022-03-06 25 | * Fix [#18](https://github.com/seancorfield/build-clj/issues/18) by using `resolve-path` from `tools.build`. 26 | * This project is now tracking `tools.build` version numbers (v0.8.0) _[I'll figure out a version schema for any releases I make between `tools.build` releases if that actually happens!]_ 27 | 28 | * v0.7.0 5d2cb60 -- 2022-02-24 29 | * Update `tools.build` to v0.8.0 for various enhancements and bug fixes. 30 | 31 | * v0.6.7 22c2d09 -- 2022-01-05 32 | * Update `build-uber-log4j2-handler` to v0.1.5 and `tools.build` to v0.7.5 for various bug fixes. 33 | 34 | * v0.6.6 171d5f1 -- 2022-01-04 35 | * Update `build-uber-log4j2-handler` to v0.1.4 for updated log4j2 dependency. 36 | 37 | * v0.6.5 972031a -- 2021-12-23 38 | * Update `tools.build` to v0.7.4 for various enhancements and bug fixes. 39 | 40 | * v0.6.4 c21cfde -- 2021-12-22 41 | * Update `tools.build` to v0.7.3 for various enhancements and bug fixes. 42 | * Update `build-uber-log4j2-handler` to v0.1.3 for updated log4j2 dependency. 43 | 44 | * v0.6.3 9b8e09b -- 2021-12-14 45 | * Update `build-uber-log4j2-handler` to v0.1.2 for updated log4j2 dependency. 46 | 47 | * v0.6.2 97c275a -- 2021-12-13 48 | * Update `tools.build` to v0.7.2 0361dde for various enhancements and bug fixes. 49 | 50 | * v0.6.1 6e962ef -- 2021-12-10 51 | * Update `build-uber-log4j2-handler` to v0.1.1 for updated log4j2 dependency. 52 | 53 | * v0.6.0 2451bea -- 2021-12-01 54 | * Provide a "slim" entry point that does not include `deps-deploy`. 55 | 56 | * v0.5.5 0527130 -- 2021-11-26 57 | * Update `tools.build` to v0.6.8 d79ae84 for `git-process` and various bug fixes. 58 | 59 | * v0.5.4 bc9c0cc -- 2021-11-13 60 | * Update `tools.build` to v0.6.5 a0c3ff6 for the improved `compile-clj` and git commands. 61 | 62 | * v0.5.3 dbf7321 -- 2021-11-08 63 | * Update `tools.build` to v0.6.3 4a1b53a for updated git deps handling. 64 | 65 | * v0.5.2 8f75b81 -- 2021-10-11 66 | * Update `tools.build` to v0.6.1 515b334 (for non-replacement on some non-text files). 67 | 68 | * v0.5.1 dc121d6 -- 2021-10-07 69 | * Support Polylith and other monorepo projects better when building library JARs by adding `:transitive` option to `jar` task. 70 | 71 | * v0.5.0 2ceb95a -- 2021-09-27 72 | * Address #10 by exposing the four `default-*` functions used to compute `target`, `class-dir`, `basis`, and `jar-file` (`uber-file`). 73 | 74 | * v0.4.0 54e39ae -- 2021-09-22 75 | * Address #6 by providing `install` task based on `tools.build` (and deprecating `:installer :local` for `deploy` task). 76 | * Update `tools.build` to v0.5.1 21da7d4. 77 | 78 | * v0.3.1 996ddfa -- 2021-09-17 79 | * Update `deps-deploy` to 0.2.0 for latest deps and new Maven settings support. 80 | 81 | * v0.3.0 fb11811 -- 2021-09-16 82 | * `:lib` is now optional for `uber` if you pass in `:uber-file`. 83 | * Update `tools.build` to v0.5.0 7d77952 (default basis is now "repro" without user `deps.edn`). 84 | 85 | * v0.2.2 5a12a1a -- 2021-09-15 86 | * Address #3 by adding an `uber` task which includes the log4j2 plugins cache conflict handler. 87 | * Added support for many more options that `tools.build` tasks accept. 88 | * Overhaul/expansion of docstrings. 89 | * Fix `:src-dirs` defaulting. 90 | * Remove spurious `println` from `uber` task. 91 | * Update `tools.build` to v0.4.0 801a22f. 92 | 93 | * v0.1.3 26b884c -- 2021-09-07 94 | * Update `tools.build` to v0.2.2 3049217. 95 | 96 | * v0.1.2 0719a09 -- 2021-08-31 97 | * Update `tools.build` to v0.2.0 7cbb94b. 98 | 99 | * v0.1.1 d096192 -- 2021-08-30 100 | * Address #1 by adding `:scm` option; `:tag` will still override `:scm {:tag ...}`. 101 | 102 | * v0.1.0 fe2d586 -- 2021-08-27 103 | * Initial release 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # build-clj 2 | 3 | Common [`tools.build`](https://github.com/clojure/tools.build) tasks abstracted into a library, building on the examples in the [official `tools.build` guide](https://clojure.org/guides/tools_build). 4 | 5 | Having implemented `build.clj` (using `tools.build`) in several of my open source projects 6 | I found there was a lot of repetition across them, so I factored out 7 | the common functionality into this library. 8 | 9 | **Caution: this wrapper has outgrown its original goal (of being a simple wrapper to eliminate boilerplate) and has far more knobs and dials than I intended, so I am deprecating it -- you should learn to use raw `tools.build` instead!** 10 | 11 | ## Use with `build.clj` 12 | 13 | Since it depends on both `tools.build` and 14 | [Erik Assum's `deps-deploy`](https://github.com/slipset/deps-deploy), 15 | your `:build` alias can just be: 16 | 17 | ```clojure 18 | :build {:deps {io.github.seancorfield/build-clj 19 | {:git/tag "v0.9.2" :git/sha "9c9f078"}} 20 | :ns-default build} 21 | ``` 22 | 23 | Your `build.clj` can start off as follows: 24 | 25 | ```clojure 26 | (ns build 27 | (:require [clojure.tools.build.api :as b] 28 | [org.corfield.build :as bb])) 29 | 30 | (def lib 'myname/mylib) 31 | ;; if you want a version of MAJOR.MINOR.COMMITS: 32 | (def version (format "1.0.%s" (b/git-count-revs nil))) 33 | ``` 34 | 35 | If you don't want `deps-deploy` -- perhaps your project is only building uberjars 36 | or you have some other deployment process for your JAR files (or perhaps you are 37 | not building JAR files at all) -- then you can specify a "slim" entry point to 38 | `build-clj` that does not include that dependency: 39 | 40 | ```clojure 41 | :build {:deps {io.github.seancorfield/build-clj 42 | {:git/tag "v0.9.2" :git/sha "9c9f078" 43 | ;; omits deps-deploy dependency: 44 | :deps/root "slim"}} 45 | :ns-default build} 46 | ``` 47 | 48 | ## Standalone CLI Usage 49 | 50 | While `build-clj` is intended primarily for use with a `build.clj` file, you 51 | can use it directly from the CLI without a `build.clj` file for many common 52 | operations. 53 | 54 | Assuming you have a `:build` alias as above (with or without `:ns-default`), 55 | you could run commands like this: 56 | 57 | ``` 58 | # run the tests using default options: 59 | clojure -T:build org.corfield.build/run-tests 60 | # clean the target folder: 61 | clojure -T:build org.corfield.build/clean 62 | # build a library JAR: 63 | clojure -T:build org.corfield.build/jar :lib myname/mylib :version '"1.0.123"' 64 | # deploy that library to Clojars: 65 | clojure -T:build org.corfield.build/deploy :lib myname/mylib :version '"1.0.123"' 66 | # build an application uberjar: 67 | clojure -T:build org.corfield.build/uber :lib myname/myapp :main my.app.core 68 | ``` 69 | 70 | ## Tasks Provided 71 | 72 | The following common build tasks are provided, all taking an options 73 | hash map as the single argument _and returning that hash map unchanged_ 74 | so you can reliably thread the build tasks. 75 | _[Several functions in `clojure.tools.build.api` return `nil` instead]_ 76 | 77 | * `clean` -- clean the target directory (wraps `delete` from `tools.build`), 78 | * `deploy` -- deploy to Clojars (wraps `deploy` from `deps-deploy`), 79 | * `install` -- install the JAR locally (wraps `create-basis` and `install` from `tools.build`), 80 | * `jar` -- build the (library) JAR and `pom.xml` files (wraps `create-basis`, `write-pom`, `copy-dir`, and `jar` from `tools.build`), 81 | * `uber` -- build the (application) uber JAR, with optional `pom.xml` file creation and/or AOT compilation (wraps `create-basis`, `write-pom`, `copy-dir`, `compile-clj`, and `uber` from `tools.build`), 82 | * `run-tests` -- run the project's tests (wraps `create-basis`, `java-command`, and `process` from `tools.build`, to run the `:main-opts` in your `:test` alias). 83 | 84 | For `deploy`, `install`, and `jar`, you must provide at least `:lib` and `:version`. 85 | For `uber`, you must provide at least `:lib` or `:uber-file` for the name of the JAR file. 86 | Everything else has "sane" defaults, but can be overridden. 87 | 88 | > Note: you can always get help for a `build.clj` file by running `clojure -A:deps -T:build help/doc` which uses the `help/doc` function from the built-in `:deps` alias in the root `deps.edn` file. 89 | 90 | ## Typical `build.clj` with `build-clj` 91 | 92 | You might typically have the following tasks in your `build.clj`: 93 | 94 | ```clojure 95 | (defn ci "Run the CI pipeline of tests (and build the JAR)." [opts] 96 | (-> opts 97 | (assoc :lib lib :version version) 98 | (bb/run-tests) 99 | (bb/clean) 100 | (bb/jar))) 101 | 102 | (defn install "Install the JAR locally." [opts] 103 | (-> opts 104 | (assoc :lib lib :version version) 105 | (bb/install))) 106 | 107 | (defn deploy "Deploy the JAR to Clojars." [opts] 108 | (-> opts 109 | (assoc :lib lib :version version) 110 | (bb/deploy))) 111 | ``` 112 | 113 | Or if you are working with an application, you might have: 114 | 115 | ```clojure 116 | (defn ci "Run the CI pipeline of tests (and build the uberjar)." [opts] 117 | (-> opts 118 | (assoc :lib lib :main main) 119 | (bb/run-tests) 120 | (bb/clean) 121 | (bb/uber))) 122 | ``` 123 | 124 | > Note: this `uber` task in `build-clj` supplies the [log4j2 conflict handler](https://github.com/seancorfield/build-uber-log4j2-handler) to the underlying `uber` task of `tools.build` so that you don't have to worry about the plugins cache files being merged. 125 | 126 | ### `pom.xml` and `jar` 127 | 128 | By default, the `jar` task calls `tools.build`'s `write-pom` function and 129 | will write `pom.xml` into `target/classes/META-INF/maven///pom.xml`. 130 | You can provide a template for that file, that contains information that 131 | `write-pom` does not provide (and does not offer options to override), such 132 | as `` and ``. Whilst that file _could_ be called 133 | `pom.xml` and would get picked up automatically by `write-pom` as the source POM, 134 | that would leave you with a potentially incomplete and/or outdated file. 135 | Instead, consider using `template/pom.xml` or something similar, and 136 | specify `:src-pom "template/pom.xml"` as an additional option to `build-clj`'s 137 | `jar` task: 138 | 139 | ```clojure 140 | (defn ci "Run the CI pipeline of tests (and build the JAR)." [opts] 141 | (-> opts 142 | (assoc :lib lib :version version :src-pom "template/pom.xml") 143 | (bb/run-tests) 144 | (bb/clean) 145 | (bb/jar))) 146 | ``` 147 | 148 | `template/pom.xml` is suggested rather than, say `pom_template.xml` at the 149 | root, so that GitHub Actions' `setup_java` still find it and recognizes the 150 | repo as being Maven-based for caching purposes (it looks for `**/pom.xml`). 151 | 152 | ## Running Tests 153 | 154 | If you want a `run-tests` task in your `build.clj`, independent of the `ci` 155 | task shown above, the following can be added: 156 | 157 | ```clojure 158 | (defn run-tests "Run the tests." [opts] 159 | (-> opts (bb/run-tests))) 160 | ``` 161 | 162 | By default, the `run-tests` task will run whatever is in your `:test` alias 163 | but if there is no `:main-opts`, it assumes Cognitect's `test-runner`: 164 | 165 | ```clojure 166 | :test 167 | {:extra-paths ["test"] 168 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 169 | io.github.cognitect-labs/test-runner 170 | {:git/tag "v0.5.0" :git/sha "48c3c67"}} 171 | :exec-fn cognitect.test-runner.api/test} 172 | ``` 173 | 174 | The above alias allows for tests to be run directly via: 175 | 176 | ```bash 177 | clojure -X:test 178 | ``` 179 | 180 | The `run-tests` task above would run the tests as if the `:test` alias 181 | also contained: 182 | 183 | ```clojure 184 | :main-opts ["-m" "cognitect.test-runner"] 185 | ``` 186 | 187 | If you want to use a different test runner with `build-clj`, just provide 188 | different dependencies and supply `:main-opts`: 189 | 190 | ```clojure 191 | ;; a :test alias that specifies the kaocha runner: 192 | :test 193 | {:extra-paths ["test"] 194 | :extra-deps {lambdaisland/kaocha {:mvn/version "1.0.887"}} 195 | :main-opts ["-m" "kaocha.runner"]} 196 | ``` 197 | 198 | With this `:test` alias, the `run-tests` task above would run your tests using Kaocha. 199 | 200 | ## Running Additional Programs 201 | 202 | In addition, there is a `run-task` function that takes an options hash 203 | map and a vector of aliases. This runs an arbitrary Clojure main function, 204 | determined by those aliases, in a subprocess. `run-tests` uses this by 205 | adding a `:test` alias and in the absence of any `:main-opts` behind those 206 | aliases, assumes it should run `cognitect.test-runner`'s `-main` function. 207 | 208 | `run-task` picks up `:jvm-opts` and `:main-opts` from the specified aliases 209 | and uses them as the `:java-args` and `:main-args` respectively in a call to 210 | `clojure.tools.build.api/java-command` to build the `java` command to run. 211 | By default, it runs `clojure.main`'s `-main` function with the specified 212 | `:main-args`. 213 | 214 | For example, if your `deps.edn` contains the following alias: 215 | 216 | ```clojure 217 | :eastwood {:extra-deps {jonase/eastwood {:mvn/version "0.5.1"}} 218 | :main-opts ["-m" "eastwood.lint" "{:source-paths,[\"src\"]}"]} 219 | ``` 220 | 221 | Then you can define an `eastwood` task in your `build.clj` file: 222 | 223 | ```clojure 224 | (defn eastwood "Run Eastwood." [opts] 225 | (-> opts (bb/run-task [:eastwood]))) 226 | ``` 227 | 228 | Or you could just make it part of your `ci` pipeline without adding that function: 229 | 230 | ```clojure 231 | (defn ci "Run the CI pipeline of tests (and build the JAR)." [opts] 232 | (-> opts 233 | (assoc :lib lib :version version) 234 | (bb/run-task [:eastwood]) 235 | (bb/run-tests) 236 | (bb/clean) 237 | (bb/jar))) 238 | ``` 239 | 240 | ## Defaults 241 | 242 | The following defaults are provided: 243 | 244 | * `:target` -- `"target"`, 245 | * `:basis` -- `(b/create-basis {})` -- this is a reproducible basis, i.e., it ignores the user `deps.edn` file -- if you want your user `deps.edn` included, you will need to explicitly pass `:basis (b/create-basis {:user :standard})` into tasks, 246 | * `:class-dir` -- `(str target "/classes")`, 247 | * `:jar-file` -- `(format \"%s/%s-%s.jar\" target lib version)`, 248 | * `:uber-file` -- `(format \"%s/%s-%s.jar\" target lib version)` if `:version` is provided, else `(format \"%s/%s-standalone.jar\" target lib)`. 249 | 250 | As of v0.5.0, the four functions that compute those defaults are exposed for use in your own `build.clj` files: 251 | * `(default-target)` -- return the default for `:target`, 252 | * `(default-basis)` -- return the default for `:basis`, 253 | * `(default-class-dir)` -- return the default for `:class-dir`; `(default-class-dir target)` is also available, 254 | * `(default-jar-file lib version)` -- return the default for `:jar-file` or `:uber-file`; `(default-jar-file target lib version)` and `(default-jar-file version)` are also available (the latter defaults `:lib` to `'application`). 255 | 256 | For the functions defined in `org.corfield.build`, you can override 257 | the high-level defaults as follows: 258 | 259 | * `clean` 260 | * `:target`, 261 | * `deploy` 262 | * Requires: `:lib` and `:version`, 263 | * `:target`, `:class-dir`, `:jar-file`, 264 | * `install` 265 | * Requires: `:lib` and `:version`, 266 | * `:target`, `:class-dir`, `:basis`, `:jar-file`, 267 | * `jar` 268 | * Requires: `:lib` and `:version`, 269 | * `:target`, `:class-dir`, `:basis`, `:resource-dirs`, `:scm`, `:src-dirs`, `:tag` (defaults to `(str "v" version)`), `:jar-file`, 270 | * `uber` 271 | * Requires: `:lib` or `:uber-file`, 272 | * `:target`, `:class-dir`, `:basis`, `:compile-opts`, `:main`, `:ns-compile`, `:resource-dirs`, `:scm`, `:src-dirs`, `:tag` (defaults to `(str "v" version)` if `:version` provided), `:version` 273 | * `run-tests` 274 | * `:aliases` -- for any additional aliases beyond `:test` which is always added, 275 | * Also accepts any options that `run-task` accepts. 276 | 277 | See the docstrings of those task functions for more detail on which options 278 | they can also accept and which additional defaults they offer. 279 | 280 | As noted above, `run-task` takes an options hash map and a vector of aliases. 281 | The following options can be provided to `run-task` to override the default 282 | behavior: 283 | 284 | * `:java-opts` -- used _instead of_ `:jvm-opts` from the aliases, 285 | * `:jvm-opts` -- used _in addition to_ the `:java-opts` vector or _in addition to_ `:jvm-opts` from the aliases, 286 | * `:main` -- used _instead of_ `'clojure.main` when building the `java` command to run, 287 | * `:main-args` -- used _instead of_ `:main-opts` from the aliases, 288 | * `:main-opts` -- used _in addition to_ the `:main-args` vector or _in addition to_ `:main-opts` from the aliases. 289 | 290 | > Note: if `:main-args` is not provided and there are no `:main-opts` in the aliases provided, the default will be `["-m" "cognitect.test-runner"]` to ensure that `run-tests` works by default without needing `:main-opts` in the `:test` alias (since it is common to want to start a REPL with `clj -A:test`). 291 | 292 | ## Monorepos and Library JARs 293 | 294 | If you are working in a monorepo, such as the [Polylith architecture](https://polylith.gitbook.io/), and need 295 | to build library JAR files from projects that rely on `:local/root` dependencies to specify other source 296 | components, you will generally want to pass `:transitive true` to the `jar` task. 297 | 298 | Without `:transitive true`, i.e., by default, the `jar` task generates a `pom.xml` from just the dependencies 299 | specified directly in the project `deps.edn` and does not consider dependencies from local source subprojects. 300 | In addition, by default `jar` only copies `src` and `resources` from the current project folder. 301 | 302 | With `:transitive true`, the `jar` task includes direct dependencies from local source subprojects when 303 | generating the `pom.xml` and will also copy all folders found on the classpath -- which is generally all 304 | of the `src` and `resources` folders from those local source subprojects. 305 | 306 | > Note: git dependencies look like local source subprojects so they will also be included if you specify `:transitive true` -- but your `pom.xml` will not contain those dependencies anyway so users of your library JAR would have a time if git folders are not copied into the JAR! 307 | 308 | # License 309 | 310 | Copyright © 2021-2022 Sean Corfield 311 | 312 | Distributed under the Apache Software License version 2.0. 313 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {io.github.clojure/tools.build {:git/tag "v0.9.2" :git/sha "fe6b140"} 3 | io.github.seancorfield/build-uber-log4j2-handler {:git/tag "v0.1.5" :git/sha "55fb6f6"} 4 | slipset/deps-deploy {:mvn/version "0.2.0"}}} 5 | -------------------------------------------------------------------------------- /slim/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {io.github.seancorfield/build-clj-slim {:local/root "../" 3 | :exclusions [slipset/deps-deploy]} 4 | io.github.clojure/tools.build {:git/tag "v0.9.2" :git/sha "fe6b140"} 5 | io.github.seancorfield/build-uber-log4j2-handler {:git/tag "v0.1.5" :git/sha "55fb6f6"}}} 6 | -------------------------------------------------------------------------------- /src/org/corfield/build.clj: -------------------------------------------------------------------------------- 1 | ;; copyright (c) 2021-2022 sean corfield, all rights reserved. 2 | 3 | (ns org.corfield.build 4 | "Common build utilities. 5 | 6 | The following high-level defaults are provided: 7 | 8 | :target \"target\", 9 | :basis (create-basis {:project \"deps.edn\"}, 10 | :class-dir (str target \"/classes\"), 11 | :jar-file (format \"%s/%s-%s.jar\" target lib version), 12 | :uber-file (format \"%s/%s-%s.jar\" target lib version) 13 | or, if :version is not provided: 14 | (format \"%s/%s-standalone.jar\" target lib) 15 | 16 | You are expected to provide :lib and :version as needed. 17 | 18 | The following build task functions are provided, with the 19 | specified required and optional hash map options: 20 | 21 | clean -- opt :target, 22 | deploy -- req :lib, :version 23 | opt :target, :class-dir, :jar-file 24 | (see docstring for additional options) 25 | install -- req :lib, :version 26 | opt :target, :class-dir, :basis, :jar-file 27 | (see docstring for additional options) 28 | jar -- req :lib, :version 29 | opt :target, :class-dir, :basis, :scm, :src-dirs, 30 | :resource-dirs, :tag, :jar-file 31 | (see docstring for additional options) 32 | uber -- req :lib or :uber-file 33 | opt :target, :class-dir, :basis, :scm, :src-dirs, 34 | :resource-dirs, :tag, :version 35 | (see docstring for additional options) 36 | run-task -- [opts aliases] 37 | opt :java-opts -- defaults to :jvm-opts from aliases 38 | :jvm-opts -- added to :java-opts 39 | :main -- defaults to clojure.main 40 | :main-args -- defaults to :main-opts from aliases 41 | :main-opts -- added to :main-args 42 | run-tests -- opt :aliases (plus run-task options) 43 | invokes (run-task opts (into [:test] aliases)) 44 | 45 | All of the above return the opts hash map they were passed 46 | (unlike some of the functions in clojure.tools.build.api). 47 | 48 | The following low-level defaults are also provided to make 49 | it easier to call the task functions here: 50 | 51 | :ns-compile if :main is provided and :sort is not, this 52 | defaults to the :main namespace (class), 53 | 54 | :scm if :tag is provided, that is used here, else 55 | if :version is provided, that is used for :tag 56 | here with \"v\" prefixed, 57 | 58 | :src-dirs [\"src\"] 59 | 60 | :src+dirs this is a synthetic option that is used for the 61 | file/directory copying that is part of `jar` and 62 | `uber` and it is computed as :src-dirs plus 63 | :resource-dirs, essentially, with the former 64 | defaulted as noted above and the latter defaulted 65 | to [\"resources\"] just for the copying but otherwise 66 | has no default (for `tools.build/write-pom`)." 67 | (:require [clojure.java.io :as io] 68 | [clojure.string :as str] 69 | [clojure.tools.build.api :as b] 70 | [clojure.tools.deps :as t] 71 | [org.corfield.log4j2-conflict-handler 72 | :refer [log4j2-conflict-handler]])) 73 | 74 | (defn default-target 75 | "Return the default target directory name." 76 | {:arglists '([])} 77 | ([] (default-target nil)) 78 | ([target] 79 | (or target "target"))) 80 | 81 | (defn default-basis 82 | "Return the default basis." 83 | {:arglists '([])} 84 | ([] (default-basis nil)) 85 | ([basis] 86 | (or basis (b/create-basis {})))) 87 | 88 | (defn default-class-dir 89 | "Return the default `class-dir`. 90 | 91 | May be passed a non-default target directory name." 92 | {:arglists '([] [target])} 93 | ([] (default-class-dir nil nil)) 94 | ([target] (default-class-dir nil target)) 95 | ([class-dir target] 96 | (or class-dir (str (default-target target) "/classes")))) 97 | 98 | (defn default-jar-file 99 | "Given the `lib` and `version`, return the default JAR 100 | filename. 101 | 102 | `lib` can be omitted and will default to `'application` 103 | (for uberjar usage). 104 | 105 | May be passed a non-default target directory name." 106 | ([version] (default-jar-file nil nil version)) 107 | ([lib version] (default-jar-file nil lib version)) 108 | ([target lib version] 109 | (format "%s/%s-%s.jar" (default-target target) (name (or lib 'application)) version))) 110 | 111 | (defn clean 112 | "Remove the target folder." 113 | [{:keys [target] :as opts}] 114 | (println "\nCleaning target...") 115 | (b/delete {:path (default-target target)}) 116 | opts) 117 | 118 | (defn- lifted-basis 119 | "This creates a basis where source deps have their primary 120 | external dependencies lifted to the top-level, such as is 121 | needed by Polylith and possibly other monorepo setups." 122 | [] 123 | (let [default-libs (:libs (b/create-basis)) 124 | source-dep? #(not (:mvn/version (get default-libs %))) 125 | lifted-deps 126 | (reduce-kv (fn [deps lib {:keys [dependents] :as coords}] 127 | (if (and (contains? coords :mvn/version) (some source-dep? dependents)) 128 | (assoc deps lib (select-keys coords [:mvn/version :exclusions])) 129 | deps)) 130 | {} 131 | default-libs)] 132 | (-> (b/create-basis {:extra {:deps lifted-deps}}) 133 | (update :libs #(into {} (filter (comp :mvn/version val)) %))))) 134 | 135 | (defn- jar-opts 136 | "Provide sane defaults for jar/uber tasks. 137 | 138 | :lib is required, :version is optional for uber, everything 139 | else is optional." 140 | [{:keys [basis class-dir conflict-handlers jar-file lib 141 | main ns-compile resource-dirs scm sort src-dirs tag 142 | target transitive uber-file version] 143 | :as opts}] 144 | (when transitive 145 | (assert (nil? basis) ":transitive cannot be true when :basis is provided")) 146 | (let [basis (if transitive 147 | (lifted-basis) 148 | (default-basis basis)) 149 | directory? #(let [f (java.io.File. %)] 150 | (and (.exists f) (.isDirectory f))) 151 | scm-default (cond tag {:tag tag} 152 | version {:tag (str "v" version)}) 153 | src-default (or src-dirs ["src"]) 154 | version (or version "standalone") 155 | xxx-file (default-jar-file target lib version)] 156 | (assoc opts 157 | :basis (default-basis basis) 158 | :class-dir (default-class-dir class-dir target) 159 | :conflict-handlers 160 | (merge log4j2-conflict-handler conflict-handlers) 161 | :jar-file (or jar-file xxx-file) 162 | :ns-compile (or ns-compile (when (and main (not sort)) 163 | [main])) 164 | :scm (merge scm-default scm) 165 | :src-dirs src-default 166 | :src+dirs (if transitive 167 | (filter directory? (:classpath-roots basis)) 168 | (into src-default 169 | (or resource-dirs ["resources"]))) 170 | :uber-file (or uber-file xxx-file)))) 171 | 172 | (defn jar 173 | "Build the library JAR file. 174 | 175 | Requires: :lib, :version 176 | 177 | Accepts any options that are accepted by: 178 | * tools.build/write-pom 179 | * tools.build/jar 180 | 181 | Writes pom.xml into META-INF in the :class-dir, then 182 | copies :src-dirs + :resource-dirs into :class-dir, then 183 | builds :jar-file into :target (directory). 184 | 185 | If you are building a JAR in a monorepo and rely on 186 | :local/root dependencies for the actual source components, 187 | such as in a Polylith project, pass :transitive true to 188 | use a 'lifted' basis and to ensure all source files are 189 | copied into the JAR." 190 | {:arglists '([{:keys [lib version 191 | basis class-dir jar-file main manifest repos 192 | resource-dirs scm src-dirs src-pom tag target 193 | transitive]}])} 194 | [{:keys [lib version] :as opts}] 195 | (assert (and lib version) "lib and version are required for jar") 196 | (let [{:keys [class-dir jar-file src+dirs] :as opts} 197 | (jar-opts opts) 198 | current-dir (System/getProperty "user.dir") 199 | current-rel #(str/replace % (str current-dir "/") "")] 200 | (println "\nWriting pom.xml...") 201 | (b/write-pom opts) 202 | (println "Copying" (str (str/join ", " (map current-rel src+dirs)) "...")) 203 | (b/copy-dir {:src-dirs src+dirs 204 | :target-dir class-dir}) 205 | (println "Building jar" (str jar-file "...")) 206 | (b/jar opts)) 207 | opts) 208 | 209 | (defn uber 210 | "Build the application uber JAR file. 211 | 212 | Requires: :lib or :uber-file 213 | 214 | Accepts any options that are accepted by: 215 | * `tools.build/write-pom` 216 | * `tools.build/compile-clj` 217 | * `tools.build/uber` 218 | 219 | The uber JAR filename is derived from :lib 220 | and :version if provided, else from :uber-file. 221 | 222 | If :version is provided, writes pom.xml into 223 | META-INF in the :class-dir, then 224 | 225 | Compiles :src-dirs into :class-dir, then 226 | copies :src-dirs and :resource-dirs into :class-dir, then 227 | builds :uber-file into :target (directory)." 228 | {:argslists '([{:keys [lib uber-file 229 | basis class-dir compile-opts filter-nses 230 | ns-compile repos resource-dirs scm sort 231 | src-dirs src-pom tag target version]}])} 232 | [{:keys [lib uber-file] :as opts}] 233 | (assert (or lib uber-file) ":lib or :uber-file is required for uber") 234 | (let [{:keys [class-dir lib ns-compile sort src-dirs src+dirs uber-file version] 235 | :as opts} 236 | (jar-opts opts)] 237 | (if (and lib version) 238 | (do 239 | (println "\nWriting pom.xml...") 240 | (b/write-pom opts)) 241 | (println "\nSkipping pom.xml because :lib and/or :version were omitted...")) 242 | (println "Copying" (str (str/join ", " src+dirs) "...")) 243 | (b/copy-dir {:src-dirs src+dirs 244 | :target-dir class-dir}) 245 | (if (or ns-compile sort) 246 | (do 247 | (println "Compiling" (str (str/join ", " (or ns-compile src-dirs)) "...")) 248 | (b/compile-clj opts)) 249 | (println "Skipping compilation because :main, :ns-compile, and :sort were omitted...")) 250 | (println "Building uberjar" (str uber-file "...")) 251 | (b/uber opts)) 252 | opts) 253 | 254 | (defn install 255 | "Install the JAR to the local Maven repo cache. 256 | 257 | Requires: :lib, :version 258 | 259 | Accepts any options that are accepted by: 260 | * `tools.build/install`" 261 | {:arglists '([{:keys [lib version 262 | basis class-dir classifier jar-file target]}])} 263 | [{:keys [lib version basis class-dir classifier jar-file target] :as opts}] 264 | (assert (and lib version) ":lib and :version are required for install") 265 | (let [target (default-target target)] 266 | (b/install {:basis (default-basis basis) 267 | :lib lib 268 | :classifier classifier 269 | :version version 270 | :jar-file (or jar-file (default-jar-file target lib version)) 271 | :class-dir (default-class-dir class-dir target)}) 272 | opts)) 273 | 274 | (defn deploy 275 | "Deploy the JAR to Clojars. 276 | 277 | Requires: :lib, :version 278 | 279 | Accepts any options that are accepted by: 280 | * `deps-deploy/deploy` 281 | 282 | If :artifact is provided, it will be used for the deploy, 283 | else :jar-file will be used (making it easy to thread 284 | options through `jar` and `deploy`, specifying just :jar-file 285 | or relying on the default value computed for :jar-file)." 286 | {:arglists '([{:keys [lib version 287 | artifact class-dir installer jar-file pom-file target]}])} 288 | [{:keys [lib version class-dir installer jar-file target] :as opts}] 289 | (assert (and lib version) ":lib and :version are required for deploy") 290 | (when (and installer (not= :remote installer)) 291 | (println ":installer" installer "is deprecated -- use install task for local deployment")) 292 | (let [target (default-target target) 293 | class-dir (default-class-dir class-dir target) 294 | jar-file (or jar-file (default-jar-file target lib version)) 295 | dd-deploy (try (requiring-resolve 'deps-deploy.deps-deploy/deploy) (catch Throwable _))] 296 | (if dd-deploy 297 | (dd-deploy (merge {:installer :remote :artifact (b/resolve-path jar-file) 298 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 299 | opts)) 300 | (throw (ex-info "deps-deploy is not available in the 'slim' build-clj" {})))) 301 | opts) 302 | 303 | (defn run-task 304 | "Run a task based on aliases. 305 | 306 | If :main-args is not provided and no :main-opts are found 307 | in the aliases, default to the Cognitect Labs' test-runner." 308 | [{:keys [java-opts jvm-opts main main-args main-opts] :as opts} aliases] 309 | (let [task (str/join ", " (map name aliases)) 310 | _ (println "\nRunning task for:" task) 311 | basis (b/create-basis {:aliases aliases}) 312 | combined (t/combine-aliases basis aliases) 313 | cmds (b/java-command 314 | {:basis basis 315 | :java-opts (into (or java-opts (:jvm-opts combined)) 316 | jvm-opts) 317 | :main (or 'clojure.main main) 318 | :main-args (into (or main-args 319 | (:main-opts combined) 320 | ["-m" "cognitect.test-runner"]) 321 | main-opts)}) 322 | {:keys [exit]} (b/process cmds)] 323 | (when-not (zero? exit) 324 | (throw (ex-info (str "Task failed for: " task) {})))) 325 | opts) 326 | 327 | (defn run-tests 328 | "Run tests. 329 | 330 | Always adds :test to the aliases." 331 | [{:keys [aliases] :as opts}] 332 | (-> opts (run-task (into [:test] aliases)))) 333 | --------------------------------------------------------------------------------