holbuild is a build tool for HOL4 projects. It lets a project describe its
sources, HOL revision, and dependencies in a holproject.toml file, then builds
logical targets such as MyTheory without requiring users to manage .uo,
.ui, .dat, or Holmake state directly.
If you know HOL and Holmake, the main difference is that holbuild is
project-oriented: the project manifest selects the HOL checkout to use, build
outputs live under .holbuild/, and dependency projects can be fetched from
exact git revisions.
holbuild is usable but still experimental. It currently supports schema 2
manifests only. The manifest format and CLI may still change before any future
upstreaming into HOL.
holbuild reserves top-level SML identifiers beginning with Holbuild for its
own generated code and runtime support. User project code should not define
values, structures, signatures, or functors with that prefix.
You need Poly/ML. The small set of HOL source files needed to compile the
holbuild executable is vendored under vendor/hol.
makeCheck the resulting binary:
bin/holbuild --versionOptional install:
make installMaintainers can update the pinned HOL revision and refresh the vendored HOL source files with:
tools/update-vendored-hol.sh [--from /path/to/HOL-checkout] <40-char-HOL-commit>By default this installs to:
$HOME/.local/bin/holbuild
You can override the destination with PREFIX, BINDIR, or DESTDIR.
Create holproject.toml in the root of your HOL project:
[holbuild]
schema = 2
minimum_version = "<MAJOR.MINOR.PATCH>" # optional; required_version is accepted as an alias
[project]
name = "example"
[dependencies.hol]
git = "https://github.com/HOL-Theorem-Prover/HOL.git"
rev = "0123456789abcdef0123456789abcdef01234567"
[build]
members = ["src"]
roots = ["src/ExampleScript.sml"]Then run:
holbuildWith no explicit command, holbuild builds the default roots. You can also build
a specific logical target:
holbuild ExampleTheoryThe target is the logical theory/module name, not an object filename. The
explicit build command remains accepted, for example holbuild build and
holbuild build ExampleTheory.
To inspect how the project is resolved, run:
holbuild contextA project must declare exactly one HOL dependency:
[dependencies.hol]
git = "https://github.com/HOL-Theorem-Prover/HOL.git"
rev = "0123456789abcdef0123456789abcdef01234567"That revision is the HOL toolchain used to analyse and build the project.
holbuild builds or reuses it under:
$HOLBUILD_CACHE/hol-toolchains/<key>/hol
--cache-dir PATH overrides the global cache location for a command.
HOLBUILD_CACHE defaults to the platform cache directory, normally:
$HOME/.cache/holbuild
Project builds do not use HOLDIR, HOLBUILD_HOLDIR, or --holdir to select
HOL. If a command needs HOL, it uses [dependencies.hol] from the manifest.
To build that HOL toolchain ahead of time, for example in CI, run:
holbuild buildholbuild is the default command, so it can usually be omitted:
holbuild # build default roots
holbuild MyTheory # build one logical target
holbuild build MyTheory # equivalent explicit formOther useful commands:
holbuild --version
holbuild --help
holbuild repl
holbuild run script.sml
holbuild context
holbuild execution-plan MyTheory:my_theorem
holbuild buildhol
holbuild heap main
holbuild export -o build-output.hbx MyTheory
holbuild import build-output.hbx
holbuild clean MyTheory
holbuild gcGlobal options go before the command, or before the target list when using the default build command:
holbuild -j4 MyTheory
holbuild --maxheap 4096 MyTheory
holbuild --source-dir /path/to/project MyTheory
holbuild --cache-dir /path/to/cache MyTheory
holbuild --json MyTheoryBuild-specific options follow the build command. If you omit build, put the
option before the targets:
holbuild build --force=project MyTheory
holbuild --no-cache MyTheory
holbuild --tactic-timeout 5 MyTheory
holbuild --watch MyTheory
holbuild --dry-run MyTheory
holbuild clean MyTheory && holbuild --no-cache MyTheoryCommon global options:
--source-dir PATH: project source directory. Defaults toHOLBUILD_SOURCE_DIRor the current working directory.--cache-dir PATH: global cache directory. OverridesHOLBUILD_CACHEand the platform default cache location.--json: emit newline-delimited JSON where supported.--quiet,--verbose,--verbosity LEVEL: adjust status output.-j N,-jN,--jobs N: build parallelism.--maxheap MB,--max-heap MB: Poly/ML maximum heap size for child HOL processes.
holbuild build --watch MyTheory runs an initial build, then watches project
inputs with inotifywait and rebuilds after changes. Watch mode currently
requires inotifywait from inotify-tools and does not support --json,
--dry-run, or --repl-on-failure.
holbuild clean MyTheory removes project-local generated artifacts, dependency
metadata, and checkpoints for the named theory target. This is primarily a
recovery/debugging command for suspect local state; normal builds should not
require it. Clean does not remove global cache entries, so use
holbuild build --no-cache MyTheory afterwards if you also want to avoid
restoring the target from the global cache.
--source-dir PATH or HOLBUILD_SOURCE_DIR chooses where to look for
holproject.toml. Build output is written under .holbuild/ in the current
working directory.
[holbuild]
schema = 2
minimum_version = "<MAJOR.MINOR.PATCH>"schema = 2 is required. minimum_version is optional; required_version is
accepted as an alias for compatibility. Set only one of them. If present, it
must be a semantic version MAJOR.MINOR.PATCH and means "this project requires
at least this holbuild version".
[project]
name = "example"The project name is used when the project is consumed as a dependency.
Direct git dependencies use exact commit hashes:
[dependencies.foo]
git = "https://github.com/acme/foo"
rev = "0123456789abcdef0123456789abcdef01234567"A dependency may also refer to a subdirectory of another direct dependency, using a shim manifest:
[dependencies.keccak]
from = "hol"
path = "examples/Crypto/Keccak"
manifest = "shims/keccak.toml"Current dependency limits:
revmust be an exact lowercase 40-character commit hash.- Branches, tags, version ranges, registries, solvers, lockfiles, path dependencies, local overrides, and multiple versions of one package are not supported.
- Direct git dependencies use only
gitandrev. - Subtree dependencies use
from,path, andmanifest. pathandmanifestmust be relative and must not contain...
[build]
members = ["src", "lib"]
exclude = ["*/selftest.sml", "*/examples/*"]
roots = ["src/MainScript.sml"]
tactic_timeout = 10.0
[build.root_tactic_timeouts]
"src/SlowScript.sml" = 60.0memberstellsholbuildwhere to discover source files.excluderemoves package-root-relative glob matches from discovery.rootsare the default entry points whenholbuild buildis run with no target. Useholbuild build --warn-unreachableto report discoverable theory scripts that are outside the root dependency closure.tactic_timeoutsets the default root-project proof-step timeout in seconds. The built-in default is2.5;0disables the timeout.root_tactic_timeoutslets individual root source files set timeout contracts for their dependency closures.
Most source dependencies are inferred automatically. Use [actions.NAME] when a
logical target needs extra information:
[actions.MyTheory]
deps = ["MyProjectLib"]
loads = ["SomeExternalLib"]
extra_deps = ["data/table.txt"]
cache = false
always_reexecute = truedepsadds logical project dependencies.loadsadds modules/libraries to load before the action.extra_depsadds filesystem inputs that should be hashed into the action key.cache = falsedisables global cache restore/publish for the action.always_reexecute = truedisables local up-to-date skipping for the action.
Source files may also declare source-file-relative extra dependencies:
val () = holbuild_extra_deps ["../data/table.txt"];Generated HOL source can be declared with [[generate]] entries:
[build]
members = ["src", "gen"]
[[generate]]
name = "opcodes"
command = ["python3", "scripts/gen_opcodes.py", "data/opcodes.toml", "-o", "gen/OpcodeScript.sml"]
inputs = ["scripts/gen_opcodes.py", "data/opcodes.toml"]
outputs = ["gen/OpcodeScript.sml"]
deps = []Generators run before source discovery. Declared outputs are checked and then scanned as normal source files.
[[heap]]
name = "main"
output = "build/main.heap"
objects = ["MainTheory"]
[run]
heap = "build/main.heap"
loads = ["MyLib"]holbuild heap main builds the listed logical objects, loads generated theory
modules, and saves the heap. holbuild run and holbuild repl create a project
run context under .holbuild/ before loading [run].loads and user arguments.
By default, holbuild instruments modern theorem proofs and executes them as
proof steps. This gives better failure locations, per-step tactic timeouts,
failed-prefix checkpoints, and optional traces.
Useful commands and options:
holbuild execution-plan MyTheory:my_theorem
holbuild build --tactic-timeout 10 MyTheory
holbuild build --trace-steps --force MyTheory
holbuild build --repl-on-failure MyTheory
holbuild build --skip-proof-steps MyTheory
holbuild build --skip-checkpoints MyTheoryexecution-plan THEORY:THEOREMprints the proof-step plan for one theorem.--tactic-timeout SECONDSchanges the per-step timeout;0disables it.--trace-stepsrecords proof-step traces in child logs.--repl-on-failurestarts a HOL REPL from the newest useful checkpoint after a theory failure. It serialises the build and is not supported with--json.--skip-proof-stepsopts out of proof-step execution.--skip-checkpointsdisables checkpoint.save/.okcreation.
For source-executed theory builds, holbuild writes a live child log at
.holbuild/logs/current/<package>/<logical>/build.log. You can inspect it during
a long build with tail -f; after the child exits, the same path is kept as the
latest log. Up-to-date and cache-restored targets do not produce a new log; use
--force --no-cache to regenerate one.
Compatibility aliases:
--skip-goalfragwarns and behaves like--skip-proof-steps.--goalfrag-tracewarns and behaves like--trace-steps.--new-iris accepted as a deprecated no-op.
Removed legacy interfaces:
goalfrag-plan--goalfrag--goalfrag-plan
Important paths:
.holbuild/src/<package> # dependency source checkouts
.holbuild/packages/<package> # package build artefacts
$HOLBUILD_CACHE/hol-toolchains/ # built HOL toolchains and analysers
The global build cache stores selected semantic artefacts such as Theory.sig,
Theory.sml, and Theory.dat by action key. Cache hits materialise validated
artefacts into the local .holbuild/ tree.
Portable build-output archives use the same cache representation:
holbuild build MyTheory
holbuild export -o my-build.hbx MyTheory
holbuild --cache-dir /path/to/other/cache import my-build.hbxexport includes the selected target closure by default and does not run a
build unless --build is passed. The archive stores a global deduplicated blob
area plus project/ and deps/<package>/ package views for the exported action
manifests. import hydrates the global cache; a later holbuild build MyTheory
materialises outputs through the normal cache-restore path.
Use holbuild export --metadata-out MyTheory.hbx.json to write a registry
metadata sidecar for static hosting or GitHub Releases; see
docs/hbx-registry.md.
Clean old project and cache state with:
holbuild gc
holbuild gc --clean-only
holbuild --cache-dir /path/to/cache gc --cache-onlygc --clean-only skips the global cache. gc --cache-only skips project
locking/discovery and does not require a HOL toolchain. Project checkpoint GC
uses .holconfig.toml's [build].checkpoint_limit_gb unless overridden with
--max-checkpoints-gb.
Local machine settings may go in .holconfig.toml:
[build]
jobs = 16
checkpoint_limit_gb = 20
exclude = ["worktrees/*"]
tactic_timeout = 10.0This is for workstation settings, not dependency overrides. build.jobs sets
local default parallelism, and build.checkpoint_limit_gb sets the local
checkpoint storage budget in GiB; the built-in checkpoint budget default is 5.
Dependency locations belong in holproject.toml.
Unknown fields in recognised holproject.toml and .holconfig.toml tables are
errors.
A project CI job should normally install or build holbuild, then run:
holbuild buildhol # optional warm-up
holbuild buildDo not pass HOLDIR to choose the project HOL. The project HOL is selected by
[dependencies.hol].
If CI builds holbuild from source, no separate HOL source checkout is needed;
run make. The pinned HOL revision is recorded in vendor/hol/REV.
Useful caches:
$HOLBUILD_CACHE/hol-toolchains, keyed by the project HOL revision and Poly/ML version.
Repository tests resolve the schema 2 HOL toolchain cache automatically:
make testTo reuse an explicit checkout instead, pass HOLDIR=/path/to/built/HOL. The
checkout must be at the revision recorded in vendor/hol/REV.
Maintainer checklist:
-
Ensure CI is green on
master. -
Bump
HolbuildVersion.versioninsml/version.smlif needed. -
Create and push an annotated tag:
version=X.Y.Z git tag -a "v$version" -m "holbuild v$version" git push origin "v$version"
-
Create a GitHub Release from that tag:
- GitHub repository → Releases → Draft a new release.
- Select the tag, for example
vX.Y.Z. - Use the tag as the title.
- Summarise user-visible changes.
There is currently no workflow that automatically publishes a GitHub Release from a pushed tag.
See DESIGN.md for design notes on project layout, dependency resolution,
action-key invalidation, analyser separation, and cache semantics.