kbuild
kbuild is the monorepo build tool for Janus. It runs actions on packages — building uberjars, deploying to Maven, tagging versions, and generating CI workflows.
kbuild is invoked as a Clojure tools function:
clojure -T:kbuild <command> <args...>
Commands
run
Executes one or more actions on packages.
By default, run operates on all changed packages concurrently (:changed defaults to true).
Use :package to restrict execution to specific packages.
# Run an action on all changed packages
clojure -T:kbuild run :action <symbol>
# Restrict to a specific package
clojure -T:kbuild run :action <symbol> :package <name>
# Multiple actions (executed sequentially per package)
clojure -T:kbuild run :actions '[<sym1> <sym2> ...]'
The action symbol controls which function is called:
-
Qualified symbols (e.g.
kbuild.uber/jar) invoke the function directly. -
Unqualified symbols (e.g.
build) resolve tokbuild.implicit/<name>, which looks up the action from the package’sdeps.edn(see Implicits).
# Build all changed packages using their implicit build action
clojure -T:kbuild run :action build
# Build all packages (ignore change detection)
clojure -T:kbuild run :action build :changed false
# Build a specific package by simple name
clojure -T:kbuild run :action kbuild.uber/jar :package chm-backend
# Build a specific package by qualified name
clojure -T:kbuild run :action kbuild.uber/jar :package janus/chm-backend
# Run multiple actions sequentially on all changed packages
clojure -T:kbuild run \
:actions '[build publish verify release]'
build, publish, test, release, and clean
Convenience shorthands that delegate to run and accept the same arguments.
| Command | Equivalent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Common arguments
| Argument | Default | Description |
|---|---|---|
|
— |
Qualified or unqualified symbol naming a single action function (for |
|
— |
Vector of action symbols for sequential execution (for |
|
— (all packages) |
Optional filter. Accepts a fully-qualified symbol ( |
|
|
When |
|
— |
Main namespace for AOT compilation and |
|
|
When |
|
— |
Maven repository name for deployment. Used by |
Implicits
Packages declare their build and publish strategy in deps.edn under the :kbuild/implicits key.
When you invoke an unqualified action (e.g. build), kbuild looks up the corresponding entry in the package’s :kbuild/implicits map and delegates to the :kbuild/action specified there.
Any extra keys in the implicits entry (e.g. :mvn/repo, :entry-point) are merged into the action opts, so packages can declare their defaults alongside the action.
Each implicit can map to either a single action definition (a map) or a vector of action definitions that are executed in sequence:
Five implicit actions are available:
| Implicit | Description |
|---|---|
|
Builds the package artifact (jar, uberjar, etc.). |
|
Publishes the built artifact to a repository. |
|
Runs tests for the package. Defaults to |
|
Verifies the package artifact. No default action — the package must explicitly declare |
|
Tags the package with its current version in git. Defaults to |
Example: Rama uberjar with Maven deploy
From systems/chm/backend/deps.edn:
{:kbuild/implicits {:build {:kbuild/action kbuild.uber/rama-jar}
:publish {:kbuild/action kbuild.maven/deploy
:mvn/repo "janus"}}}
Running clojure -T:kbuild build :package chm-backend builds a Rama-compatible uberjar.
Running clojure -T:kbuild publish :package chm-backend deploys it to the janus Maven repository (the :mvn/repo value comes from the implicits config).
Running clojure -T:kbuild release :package chm-backend tags the package with its current version (uses the default kbuild.git/tag-version action).
Example: Skinny jar with Docker publish
From systems/chm/api/deps.edn:
{:kbuild/implicits {:build {:kbuild/action kbuild.pack/skinny
:entry-point
io.forward-publishing.janus.facade.main}
:publish {:kbuild/action kbuild.docker/build+push}}}
Running clojure -T:kbuild build :package chm-api builds a skinny jar with the specified entry point.
Running clojure -T:kbuild publish :package chm-api builds and pushes the Docker image.
Running clojure -T:kbuild release :package chm-api tags the package with its current version (uses the default kbuild.git/tag-version action).
The :entry-point triggers AOT compilation and sets Main-Class in the jar manifest, so the result can be run with java -jar.
Example: Grype vulnerability scan
Packages that produce Docker images can declare the verify implicit to run a Grype scan after publishing.
The package must explicitly declare the action in its deps.edn:
From systems/chm/api/deps.edn:
{:kbuild/implicits {:verify {:kbuild/action kbuild.grype/scan}}}
Running clojure -T:kbuild run :action verify :package janus.chm/api scans the :latest-tagged local Docker image, prints a colored severity summary, and fails the build if any CRITICAL vulnerabilities are found.
The JSON report is written to reports/grype/<image-name>-grype-report.json.
Example: Custom test action
If a package needs a different test runner, override the test implicit in its deps.edn:
{:kbuild/implicits {:test {:kbuild/action my.actions/test}}}
Without this override, clojure -T:kbuild test :package <name> runs the default clojure -M:test in the package directory.
Example: Multiple actions per implicit
A single implicit can run multiple actions in sequence by using a vector of action definitions instead of a single map.
For example, to publish both a Docker image and a Maven artifact when publish is invoked:
{:kbuild/implicits {:publish [{:kbuild/action kbuild.docker/build+push
:docker/image-refs ["..."]}
{:kbuild/action kbuild.maven/deploy
:mvn/repo "janus"}]}}
Running clojure -T:kbuild publish :package <name> executes kbuild.docker/build+push first, then kbuild.maven/deploy.
Each action definition in the vector is independent and receives its own opts merged with the command-line arguments.
Available actions
See Actions Reference for the full reference of all available actions.
CircleCI integration
kbuild generates dynamic CircleCI continuation workflows.
The setup config (.circleci/config.yml) calls circleci-workflow to produce a continuation YAML that is fed back into CircleCI via the continuation orb.
clojure -T:kbuild circleci-workflow \
:publish true \
:changed true \
:out '".circleci/janus-workflow.yml"'
How it works
-
The generator reads a YAML template (
.circleci/janus-template.yml) that defines thebuild-packagejob. -
It discovers all packages via kmono and creates a per-package job entry in a
janusworkflow. -
Components (packages under
components/) only run tests. -
Projects (packages under
systems/) run build actions, and optionally publish and release. -
Inter-package dependencies are translated into CircleCI job
requiresclauses.
Options
| Option | Default | Description |
|---|---|---|
|
|
Include |
|
— |
Filter to changed packages. Pass |
|
|
Path to the YAML template file. |
|
stdout |
Output file path for the generated workflow YAML. |