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 to kbuild.implicit/<name>, which looks up the action from the package’s deps.edn (see Implicits).

Examples
# 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

clojure -T:kbuild build

run :action build (all changed packages)

clojure -T:kbuild build :package <name>

run :action build :package <name>

clojure -T:kbuild publish :package <name>

run :action publish :package <name>

clojure -T:kbuild test :package <name>

run :action test :package <name>

clojure -T:kbuild release :package <name>

run :action release :package <name>

clojure -T:kbuild clean :package <name>

run :action kbuild.build/clean :package <name>

Common arguments

Argument Default Description

:action

 — 

Qualified or unqualified symbol naming a single action function (for run).

:actions

 — 

Vector of action symbols for sequential execution (for run). Mutually exclusive with :action.

:package

 — (all packages)

Optional filter. Accepts a fully-qualified symbol (janus/chm-backend), simple symbol (chm-backend), or a vector of symbols.

:changed

true

When true, only operate on packages changed since the last version tag. When a string (e.g. "master"), compares against that git ref. Set to false to include all packages.

:entry-point

 — 

Main namespace for AOT compilation and Main-Class manifest entry. Used by kbuild.uber/jar, kbuild.pack/skinny, and kbuild.pack/one-jar.

:push-tags

false

When true, pushes git tags to the remote after tagging. Used by kbuild.git/tag-version.

:mvn/repo

 — 

Maven repository name for deployment. Used by kbuild.maven/deploy.

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

build

Builds the package artifact (jar, uberjar, etc.).

publish

Publishes the built artifact to a repository.

test

Runs tests for the package. Defaults to clojure -M:test (via kbuild.test/test) — no per-package config needed. Override per-package with :kbuild/implicits {:test {:kbuild/action custom/fn}}.

verify

Verifies the package artifact. No default action — the package must explicitly declare :kbuild/implicits {:verify {:kbuild/action <fn>}} in its deps.edn.

release

Tags the package with its current version in git. Defaults to kbuild.git/tag-version — no per-package config needed. Override with :kbuild/implicits {:release {:kbuild/action custom/fn}}.

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

  1. The generator reads a YAML template (.circleci/janus-template.yml) that defines the build-package job.

  2. It discovers all packages via kmono and creates a per-package job entry in a janus workflow.

  3. Components (packages under components/) only run tests.

  4. Projects (packages under systems/) run build actions, and optionally publish and release.

  5. Inter-package dependencies are translated into CircleCI job requires clauses.

Options

Option Default Description

:publish

true

Include publish and release actions for project packages.

:changed

 — 

Filter to changed packages. Pass true to detect changes since the last version tag, or a branch name (e.g. "master") to compare against.

:template

.circleci/janus-template.yml

Path to the YAML template file.

:out

stdout

Output file path for the generated workflow YAML.