Skip to content
This repository has been archived by the owner on Feb 24, 2023. It is now read-only.

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
Lifted from and tested against depstar, expectations, honeysql,
and next.jdbc.
  • Loading branch information
seancorfield committed Aug 28, 2021
1 parent 91ff79d commit fe2d586
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 13 deletions.
36 changes: 23 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
pom.xml
pom.xml.asc
*.jar
*.class
/lib/
/classes/
/target/
/checkouts/
.lein-deps-sum
.lein-repl-history
.lein-plugins/
.lein-failures
.classpath
.clj-kondo/.cache
.cpcache
.eastwood
.factorypath
.java-version
.lsp/sqlite.db
.nrepl-port
.cpcache/
.project
.rebel_readline_history
.settings
.socket-repl-port
.sw*
.vscode
*.class
*.jar
*.swp
*~
/classes
/classes
/clojure_test_*
/cljs-test-runner-out
/derby.log
/target
154 changes: 154 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,156 @@
# build-clj

Common build tasks abstracted into a library.

Having implemented `build.clj` in several of my open source projects
I found there was a lot of repetition across them, so I factored out
the common functionality into this library.

Since it depends on `tools.build` and
[Erik Assum's `deps-deploy`](https://github.com/slipset/deps-deploy),
your `:build` alias can just be:

```clojure
:build {:deps {io.github.seancorfield/build-clj
{:git/tag "v0.1.0" :git/sha "..."}}
:ns-default build}
```

Your `build.clj` can start off as follows:

```clojure
(ns build
(:require [clojure.tools.build.api :as b]
[org.corfield.build :as bb]))

(def lib 'myname/mylib)
;; if you want a version of MAJOR.MINOR.COMMITS:
(def version (format "1.0.%s" (b/git-count-revs nil)))
```

The following common build tasks are provided, all taking an options
hash map as the single argument _and returning that hash map unchanged_
so you can reliably thread the build tasks.
_[Several functions in `clojure.tools.build.api` return `nil` instead]_

* `clean` -- clean the target directory,
* `deploy` -- deploy to Clojars,
* `jar` -- build the (library) JAR and `pom.xml` files,
* `run-tests` -- run the project's tests.

For `deploy` and `jar`, you must provide at least `:lib` and `:version`.
Everything else has "sane" defaults, but can be overridden.

You might typically have the following tasks in your `build.clj`:

```clojure
(defn ci "Run the CI pipeline of tests (and build the JAR)." [opts]
(-> opts
(assoc :lib lib :version version)
(bb/run-tests)
(bb/clean)
(bb/jar)))

(defn deploy "Deploy the JAR to Clojars." [opts]
(-> opts
(assoc :lib lib :version version)
(bb/deploy)))
```

In addition, there is a `run-task` function that takes an options hash
map and a vector of aliases. This runs an arbitrary Clojure main function,
determined by those aliases, in a subprocess. `run-tests` uses this by
adding a `:test` alias and in the absence of any `:main-opts` behind those
aliases, assumes it should run `cognitect.test-runner`'s `-main` function.

If you want a `run-tests` task in your `build.clj`, independent of the `ci`
task shown above, the following can be added:

```clojure
(defn run-tests "Run the tests." [opts]
(-> opts (bb/run-tests)))
```

`run-task` picks up `:jvm-opts` and `:main-opts` from the specified aliases
and uses them as the `:java-args` and `:main-args` respectively in a call to
`clojure.tools.build.api/java-command` to build the `java` command to run.
By default, it runs `clojure.main`'s `-main` function with the specified
`:main-args`.

For example, if your `deps.edn` contains the following alias:

```clojure
:eastwood {:extra-deps {jonase/eastwood {:mvn/version "0.5.1"}}
:main-opts ["-m" "eastwood.lint" "{:source-paths,[\"src\"]}"]}
```

Then you can define an `eastwood` task in your `build.clj` file:

```clojure
(defn eastwood "Run Eastwood." [opts]
(-> opts (bb/run-task [:eastwood])))
```

Or you could just make it part of your `ci` pipeline without adding that function:

```clojure
(defn ci "Run the CI pipeline of tests (and build the JAR)." [opts]
(-> opts
(assoc :lib lib :version version)
(bb/run-task [:eastwood])
(bb/run-tests)
(bb/clean)
(bb/jar)))
```

## Defaults

The following defaults are provided:

* `:target` -- `"target"`,
* `:basis` -- `(create-basis {:project "deps.edn"}`,
* `:class-dir` -- `(str target "/classes")`,
* `:jar-file` -- `(format \"%s/%s-%s.jar\" target lib version)`.

For the functions defined in `org.corfield.build`, you can override
the defaults as follows:

* `clean`
* `:target`,
* `deploy`
* Requires: `:lib` and `:version`,
* `:target`, `:class-dir`, `:jar-file`,
* `jar`
* Requires: `:lib` and `:version`,
* `:target`, `:class-dir`, `:basis`, `:src-dirs`, `:tag` (defaults to `(str "v" version)`), `:jar-file`,
* `run-tests`
* `:aliases` -- for any additional aliases beyond `:test` which is always added,
* Also accepts any options that `run-task` accepts.

As noted above, `run-task` takes an options hash map and a vector of aliases.
The following options can be provided to `run-task` to override the default
behavior:

* `:java-opts` -- used _instead of_ `:jvm-opts` from the aliases,
* `:jvm-opts` -- used _in addition to_ the `:java-opts` vector or _in addition to_ `:jvm-opts` from the aliases,
* `:main` -- used _instead of_ `'clojure.main` when building the `java` command to run,
* `:main-args` -- used _instead of_ `:main-opts` from the aliases,
* `:main-opts` -- used _in addition to_ the `:main-args` vector or _in addition to_ `:main-opts` from the aliases.

> 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`).
## Projects Using `build-clj`

You can see how `build-clj` is used to reduce boilerplate in the
`build.clj` file of the following projects:

* [`depstar`](https://github.com/seancorfield/depstar/blob/develop/build.clj)
* [`expectations`](https://github.com/clojure-expectations/clojure-test/blob/develop/build.clj)
* [`honeysql`](https://github.com/seancorfield/honeysql/blob/develop/build.clj)
* [`next.jdbc`](https://github.com/seancorfield/next-jdbc/blob/develop/build.clj)

# License

Copyright © 2021 Sean Corfield

Distributed under the Apache Software License version 2.0.
3 changes: 3 additions & 0 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{:deps
{io.github.clojure/tools.build {:git/tag "v0.1.9" :git/sha "6736c83"}
io.github.slipset/deps-deploy {:sha "b4359c5d67ca002d9ed0c4b41b710d7e5a82e3bf"}}}
119 changes: 119 additions & 0 deletions src/org/corfield/build.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
;; copyright (c) 2021 sean corfield, all rights reserved.

(ns org.corfield.build
"Common build utilities.
The following defaults are provided:
:target \"target\",
:basis (create-basis {:project \"deps.edn\"},
:class-dir (str target \"/classes\"),
:jar-file (format \"%s/%s-%s.jar\" target lib version)
You are expected to provide :lib and :version as needed.
The following build functions are provided, with the
specified required and optional hash map options:
clean -- opt :target,
deploy -- req :lib, :version
opt :target, :class-dir, :jar-file
jar -- req :lib, :version
opt :target, :class-dir, :basis, :src-dirs, :tag, :jar-file,
run-task -- [opts aliases]
opt :java-opts -- defaults to :jvm-opts from aliases
:jvm-opts -- added to :java-opts
:main -- defaults to clojure.main
:main-args -- defaults to :main-opts from aliases
:main-opts --
run-tests -- opt :aliases (plus run-task options)
invokes (run-task opts (into [:test] aliases))
All of the above return the opts hash map they were passed
(unlike some of the functions in clojure.tools.build.api)."
(:require [clojure.tools.build.api :as b]
[clojure.tools.deps.alpha :as t]
[deps-deploy.deps-deploy :as dd]))

(def ^:private default-target "target")
(def ^:private default-basis (b/create-basis {:project "deps.edn"}))
(defn- default-class-dir [target] (str target "/classes"))
(defn- default-jar-file [target lib version]
(format "%s/%s-%s.jar" target (name lib) version))

(defn clean
"Remove the target folder."
[{:keys [target] :as opts}]
(println "\nCleaning target...")
(b/delete {:path (or target default-target)})
opts)

(defn jar
"Build the library JAR file.
Requires: lib, version"
[{:keys [target class-dir lib version basis src-dirs tag jar-file] :as opts}]
(assert (and lib version) "lib and version are required for jar")
(let [target (or target default-target)
class-dir (or class-dir (default-class-dir target))
basis (or basis default-basis)
src-dirs (or src-dirs ["src"])
tag (or tag (str "v" version))
jar-file (or jar-file (default-jar-file target lib version))]
(println "\nWriting pom.xml...")
(b/write-pom {:class-dir class-dir
:lib lib
:version version
:scm {:tag tag}
:basis basis
:src-dirs src-dirs})
(println "Copying src...")
(b/copy-dir {:src-dirs src-dirs
:target-dir class-dir})
(println (str "Building jar " jar-file "..."))
(b/jar {:class-dir class-dir
:jar-file jar-file}))
opts)

(defn deploy
"Deploy the JAR to Clojars.
Requires: lib, version"
[{:keys [target class-dir lib version jar-file] :as opts}]
(assert (and lib version) "lib and version are required for deploy")
(let [target (or target default-target)
class-dir (or class-dir (default-class-dir target))
jar-file (or jar-file (default-jar-file target lib version))]
(dd/deploy (merge {:installer :remote :artifact jar-file
:pom-file (b/pom-path {:lib lib :class-dir class-dir})}
opts)))
opts)

(defn run-task
"Run a task based on aliases.
If :main-args is not provided and not :main-opts are found
in the aliases, default to the Cognitect Labs' test-runner."
[{:keys [java-opts jvm-opts main main-args main-opts] :as opts} aliases]
(println "\nRunning task for:" aliases)
(let [basis (b/create-basis {:aliases aliases})
combined (t/combine-aliases basis aliases)
cmds (b/java-command
{:basis basis
:java-opts (into (or java-opts (:jvm-opts combined))
jvm-opts)
:main (or 'clojure.main main)
:main-args (into (or main-args
(:main-opts combined)
["-m" "cognitect.test-runner"])
main-opts)})
{:keys [exit]} (b/process cmds)]
(when-not (zero? exit)
(throw (ex-info (str "Task failed for: " aliases) {}))))
opts)

(defn run-tests
"Run tests.
Always adds :test to the aliases."
[{:keys [aliases] :as opts}]
(-> opts (run-task (into [:test] aliases))))

0 comments on commit fe2d586

Please sign in to comment.