• This repository has been archived on 24/Feb/2023
  • Stars
    star
    155
  • Rank 240,864 (Top 5 %)
  • Language
    Clojure
  • License
    Apache License 2.0
  • Created about 3 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Common build tasks abstracted into a library.

build-clj

Common tools.build tasks abstracted into a library, building on the examples in the official tools.build guide.

Having implemented build.clj (using tools.build) 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.

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!

Use with build.clj

Since it depends on both tools.build and Erik Assum's deps-deploy, your :build alias can just be:

  :build {:deps {io.github.seancorfield/build-clj
                 {:git/tag "v0.9.2" :git/sha "9c9f078"}}
          :ns-default build}

Your build.clj can start off as follows:

(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)))

If you don't want deps-deploy -- perhaps your project is only building uberjars or you have some other deployment process for your JAR files (or perhaps you are not building JAR files at all) -- then you can specify a "slim" entry point to build-clj that does not include that dependency:

  :build {:deps {io.github.seancorfield/build-clj
                 {:git/tag "v0.9.2" :git/sha "9c9f078"
                  ;; omits deps-deploy dependency:
                  :deps/root "slim"}}
          :ns-default build}

Standalone CLI Usage

While build-clj is intended primarily for use with a build.clj file, you can use it directly from the CLI without a build.clj file for many common operations.

Assuming you have a :build alias as above (with or without :ns-default), you could run commands like this:

# run the tests using default options:
clojure -T:build org.corfield.build/run-tests
# clean the target folder:
clojure -T:build org.corfield.build/clean
# build a library JAR:
clojure -T:build org.corfield.build/jar :lib myname/mylib :version '"1.0.123"'
# deploy that library to Clojars:
clojure -T:build org.corfield.build/deploy :lib myname/mylib :version '"1.0.123"'
# build an application uberjar:
clojure -T:build org.corfield.build/uber :lib myname/myapp :main my.app.core

Tasks Provided

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 (wraps delete from tools.build),
  • deploy -- deploy to Clojars (wraps deploy from deps-deploy),
  • install -- install the JAR locally (wraps create-basis and install from tools.build),
  • jar -- build the (library) JAR and pom.xml files (wraps create-basis, write-pom, copy-dir, and jar from tools.build),
  • 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),
  • 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).

For deploy, install, and jar, you must provide at least :lib and :version. For uber, you must provide at least :lib or :uber-file for the name of the JAR file. Everything else has "sane" defaults, but can be overridden.

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.

Typical build.clj with build-clj

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

(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 install "Install the JAR locally." [opts]
  (-> opts
      (assoc :lib lib :version version)
      (bb/install)))

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

Or if you are working with an application, you might have:

(defn ci "Run the CI pipeline of tests (and build the uberjar)." [opts]
  (-> opts
      (assoc :lib lib :main main)
      (bb/run-tests)
      (bb/clean)
      (bb/uber)))

Note: this uber task in build-clj supplies the log4j2 conflict handler to the underlying uber task of tools.build so that you don't have to worry about the plugins cache files being merged.

pom.xml and jar

By default, the jar task calls tools.build's write-pom function and will write pom.xml into target/classes/META-INF/maven/<group>/<artifact>/pom.xml. You can provide a template for that file, that contains information that write-pom does not provide (and does not offer options to override), such as <description> and <licenses>. Whilst that file could be called pom.xml and would get picked up automatically by write-pom as the source POM, that would leave you with a potentially incomplete and/or outdated file. Instead, consider using template/pom.xml or something similar, and specify :src-pom "template/pom.xml" as an additional option to build-clj's jar task:

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

template/pom.xml is suggested rather than, say pom_template.xml at the root, so that GitHub Actions' setup_java still find it and recognizes the repo as being Maven-based for caching purposes (it looks for **/pom.xml).

Running Tests

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

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

By default, the run-tests task will run whatever is in your :test alias but if there is no :main-opts, it assumes Cognitect's test-runner:

  :test
  {:extra-paths ["test"]
   :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}
                io.github.cognitect-labs/test-runner
                {:git/tag "v0.5.0" :git/sha "48c3c67"}}
   :exec-fn cognitect.test-runner.api/test}

The above alias allows for tests to be run directly via:

clojure -X:test

The run-tests task above would run the tests as if the :test alias also contained:

   :main-opts ["-m" "cognitect.test-runner"]

If you want to use a different test runner with build-clj, just provide different dependencies and supply :main-opts:

  ;; a :test alias that specifies the kaocha runner:
  :test
  {:extra-paths ["test"]
   :extra-deps {lambdaisland/kaocha {:mvn/version "1.0.887"}}
   :main-opts ["-m" "kaocha.runner"]}

With this :test alias, the run-tests task above would run your tests using Kaocha.

Running Additional Programs

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.

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:

  :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:

(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:

(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 -- (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,
  • :class-dir -- (str target "/classes"),
  • :jar-file -- (format \"%s/%s-%s.jar\" target lib version),
  • :uber-file -- (format \"%s/%s-%s.jar\" target lib version) if :version is provided, else (format \"%s/%s-standalone.jar\" target lib).

As of v0.5.0, the four functions that compute those defaults are exposed for use in your own build.clj files:

  • (default-target) -- return the default for :target,
  • (default-basis) -- return the default for :basis,
  • (default-class-dir) -- return the default for :class-dir; (default-class-dir target) is also available,
  • (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).

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

  • clean
    • :target,
  • deploy
    • Requires: :lib and :version,
    • :target, :class-dir, :jar-file,
  • install
    • Requires: :lib and :version,
    • :target, :class-dir, :basis, :jar-file,
  • jar
    • Requires: :lib and :version,
    • :target, :class-dir, :basis, :resource-dirs, :scm, :src-dirs, :tag (defaults to (str "v" version)), :jar-file,
  • uber
    • Requires: :lib or :uber-file,
    • :target, :class-dir, :basis, :compile-opts, :main, :ns-compile, :resource-dirs, :scm, :src-dirs, :tag (defaults to (str "v" version) if :version provided), :version
  • run-tests
    • :aliases -- for any additional aliases beyond :test which is always added,
    • Also accepts any options that run-task accepts.

See the docstrings of those task functions for more detail on which options they can also accept and which additional defaults they offer.

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).

Monorepos and Library JARs

If you are working in a monorepo, such as the Polylith architecture, and need to build library JAR files from projects that rely on :local/root dependencies to specify other source components, you will generally want to pass :transitive true to the jar task.

Without :transitive true, i.e., by default, the jar task generates a pom.xml from just the dependencies specified directly in the project deps.edn and does not consider dependencies from local source subprojects. In addition, by default jar only copies src and resources from the current project folder.

With :transitive true, the jar task includes direct dependencies from local source subprojects when generating the pom.xml and will also copy all folders found on the classpath -- which is generally all of the src and resources folders from those local source subprojects.

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!

License

Copyright © 2021-2022 Sean Corfield

Distributed under the Apache Software License version 2.0.

More Repositories

1

honeysql

Turn Clojure data structures into SQL
Clojure
1,763
star
2

next-jdbc

A modern low-level Clojure wrapper for JDBC-based access to databases.
Clojure
752
star
3

dot-clojure

My .clojure/deps.edn file
Clojure
615
star
4

deps-new

Create new projects for the Clojure CLI / deps.edn
Clojure
353
star
5

usermanager-example

A little demo web app in Clojure, using Component, Ring, Compojure, Selmer (and a database)
Clojure
338
star
6

readme

A testing library that turns your README into executable Clojure tests!
Clojure
142
star
7

vscode-calva-setup

My VS Code / Calva / Portal / Joyride setup
Clojure
93
star
8

om-sente

Playground to create Om + Sente test app
JavaScript
46
star
9

jsql

Basic DSL for generating SQL/DDL, formerly java.jdbc.sql and java.jdbc.ddl
Clojure
43
star
10

boot-tools-deps

A Boot task (deps) that wraps tools.deps(.alpha) to read deps.edn files
Clojure
39
star
11

engine

A Clojure library to implement a query -> logic -> updates workflow, to separate persistence updates from business logic, to improve testing etc.
Clojure
22
star
12

clj-soap

Fork of https://bitbucket.org/taka2ru/clj-soap updated to latest Clojure version
Clojure
21
star
13

lein-fregec

A Leiningen plugin to compile Frege (http://www.frege-lang.org) code.
Clojure
20
star
14

atom-chlorine-setup

My Atom / Chlorine setup
Clojure
17
star
15

polylith-external-test-runner

An external (subprocess) test runner for Polylith
Clojure
13
star
16

socket-rebl

A Socket REPL that also submits forms to Cognitect's REBL
Clojure
13
star
17

next.jdbc.xt

Experimental extension of next.jdbc to work with XTDB 2.0 (snapshots)
Clojure
12
star
18

ring-cfml

A version of Ring (Clojure) for CFML
ColdFusion
12
star
19

logging4j2

A Clojure wrapper for log4j2
Clojure
11
star
20

java-clojure-example

Trivial example to show using Java from Clojure in deps.edn project
Clojure
9
star
21

datamapper

A couple of CFCs from the World Singles data mapper to show how we wrap Clojure (vectors of) hashmaps to present a thin OO veneer to our CFML code.
ColdFusion
8
star
22

liquid-setup

Configuration for the Liquid in-process Clojure editor
Clojure
7
star
23

boot-kotlinc

A Kotlin compilation task for Boot
Clojure
6
star
24

macro-day

Code examples I wrote during Amit Rathore's "A day with Clojure macros" training
Clojure
6
star
25

build-uber-log4j2-handler

A conflict handler for log4j2 plugins cache files for the tools.build uber task.
Clojure
6
star
26

edmund

Edmund is an Event-Driven Model micro-framework for CFML / ColdFusion
ColdFusion
5
star
27

avowal

Futures and Promises for modern CFML, inspired by my earlier cfconcurrency library
ColdFusion
3
star
28

datafy-nav-example

Examples of datafy and nav
Clojure
3
star
29

lein-fw1

A Leiningen plugin to create and manage FW/1 projects.
Clojure
3
star
30

cfml-interop

CFML/Clojure interop library extracted from World Singles code and open sourced!
Clojure
3
star
31

clojure-dining-car

A Categorized and Annotated Directory of Clojure Libraries
2
star
32

intro2fp

Code samples for Introduction to Functional Programming talk, cf.Objective() 2011
ColdFusion
2
star
33

cat-genetics

Utilities to help calculate TICA-specific color cat genetics etc
Clojure
2
star
34

clojure-lucee

A toy example of running CFML pages via Lucee as an embedded engine in a Clojure Ring application.
Clojure
2
star
35

lightstuff

Historical archive of Peter Bell's LightBase and LightGen utility framework/code
ColdFusion
2
star
36

orm-blog

A very simple blog/cms built in ColdFusion using Framework 1 and ORM.
ColdFusion
2
star
37

poly-classloader-bug

Repro for a potential classloader bug for poly test
Clojure
1
star
38

dojo-anthem

Team 1's code from the January 2013 San Francisco Clojure Dojo - to play the National Anthem
Clojure
1
star
39

cursive-expectations

Example of using Expectations with Cursive
Clojure
1
star
40

spd1

Introduction to Systematic Program Design Part 1 - some Clojure examples
Clojure
1
star
41

ordered-subset

Clojure
1
star
42

HUnitFrege

A port of HUnit from Haskell to Frege (WIP -- not even compiling yet!)
Frege
1
star
43

cfo2013

Code examples for my cf.Objective() 2013 presentations
ColdFusion
1
star
44

clojure-test

A place to discuss clojure.test concerns and possible enhancements
1
star
45

boot-localrepo

Boot tasks that wrap lein-localrepo functionality
Clojure
1
star
46

seancorfield.github.io

An Architect's View - my blog and personal web site
HTML
1
star
47

polylith-issue146

Repro for the setup-fn problem with the issue-146 branch
Clojure
1
star
48

WeeklyWrongChallenge

The weekly challenge repo for WPW
Clojure
1
star
49

ring-async-bug

SSCCE of Ring with async on Jetty that fails with thread death
Clojure
1
star
50

avaus-tv

Simulation of Avaus-TV prize draw, in Clojure
Clojure
1
star
51

hello-compojure

The Compojure template, but without lein-ring
Clojure
1
star
52

cfbloggers

Clojure scratch code to parse and analyze the feeds at coldfusionbloggers.org
Clojure
1
star
53

boxlang-clojure

An example of calling Clojure code from BoxLang
Clojure
1
star