holbuild is a build tool for HOL4 projects. It lets a project describe its
sources, HOL revision, and dependencies in a holproject.toml file (or a
holbuild.toml, see Manifest discovery), 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 supports only the current
manifest language, with an optional legacy schema = 2 marker. 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.
The same holbuild executable supports the current HOL pin in vendor/hol/REV
and the legacy compatibility floor recorded in tests/HOL_LEGACY_TACTICS_REV
(e395eb6e69054ff6f7cef9d1107fd1a04dd5848f). CI covers those two revisions;
this is not a guarantee for every intervening or future HOL commit.
Compatibility boundaries select implementations by the installed HOL API,
not branch names or revision ordering. In particular, the analyser retains its
legacy planner when ProofStepPlan is absent and supports both shared-parser
constructor shapes, before and after HOL added combinator-token spans. Small
adapter tests cover both shapes without adding a third HOL toolchain to CI.
The new token annotations do not change existing proof-step labels or extents.
You need Poly/ML. The small set of HOL source files needed to compile the
holbuild executable is vendored under vendor/hol.
makeIf polyc is not on PATH, select it at build time with POLYC:
make POLYC=/custom/polyml/bin/polycPOLYC selects the compiler used to build the holbuild executable. It is
separate from HOLBUILD_POLY, which selects the poly executable used when
holbuild builds or runs project HOL toolchains.
Check 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.toml
works too; see Manifest discovery):
[holbuild]
schema = 2
minimum_version = "<MAJOR.MINOR.PATCH>" # optional version floor
[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. A target
that looks like a source path (it contains / or ends in .sml/.sig) is
resolved relative to the current directory instead, so holbuild src/ExampleScript.sml builds the same target. The explicit build command
remains accepted, for example holbuild build and holbuild build ExampleTheory.
To inspect how the project is resolved, run:
holbuild context
holbuild context --trknl # inspect the tracing-kernel contextcontext resolves metadata for the selected kernel variant without building the
HOL toolchain.
A 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 checkout used to analyse and build the project.
holbuild builds or reuses it under:
$HOLBUILD_CACHE/hol-toolchains/<key>/hol
Only one holbuild process builds or restores a given toolchain at a time. Another process that needs the same toolchain waits for it, however long that takes, and reports the owning process on stderr when the wait starts and every 30 s.
For schema 2 projects, the shared HOL toolchain is warmed with HOL's reduced
upto-hol build sequence rather than a full default HOL build:
bin/build --no-helpdocs --seq=tools/sequences/upto-holThis produces the standard HOL executable, hol.state, Holmake, and the base
sigobj context needed by normal project builds. Source directories that would
be reached by HOL's default build after this reduced toolchain sequence are
exposed as an implicit package named hol and built by holbuild on demand. The
generated implicit HOL source manifest is cached next to the shared toolchain as
hol-source.manifest.toml with a companion hol-source.members file.
While HOL issue HOL-Theorem-Prover/HOL#2021 remains
unfixed, holbuild uses a narrow temporary parser for Holmake --json target
lines when generating that manifest. This is intended to be replaced by proper
JSON parsing once the pinned HOL revision provides valid Holmake JSON output.
--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 buildholProof-tracing builds use HOL's tracing kernel:
HOLBUILD_POLY=/path/to/tracing-poly holbuild buildhol --trknl
HOLBUILD_POLY=/path/to/tracing-poly holbuild build --trknl MyTheory--trknl requires a Poly/ML build that provides the tracing-kernel export
support used by HOL. CI tracks the exportSmall branch of
https://github.com/digama0/polyml; local users should set HOLBUILD_POLY to the
corresponding poly executable when warming or using a tracing toolchain.
Standard and tracing HOL builds have distinct toolchain identities and are
cached separately under $HOLBUILD_CACHE/hol-toolchains/.
A tracing build records the aggregate proof trace for each theory as
MyTheory.tr.gz. The trace is a normal build output: holbuild requires it for
up-to-date checks, includes it in output metadata, and publishes and restores it
through the action cache. Standard-kernel builds do not require trace outputs.
Tracing is a property of the selected bootstrapped HOL kernel; holbuild does not
pass --trknl to each child HOL process.
build --trknl collects traces, not proof-development artifacts, so it builds
root-package theories as --skip-checkpoints --skip-proof-steps would, as
dependency packages always are. Kernel inference checking and the .tr.gz
outputs are unaffected. An explicit debugging flag turns back on what it needs:
--trace-steps and --tactic-timeout enable proof steps, and
--repl-on-failure enables proof steps and checkpoints. Explicit
--skip-proof-steps or --skip-checkpoints still conflicts with those flags.
build 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 context --trknl
holbuild execution-plan MyTheory:my_theorem
holbuild check MyTheory
holbuild buildhol
holbuild buildhol --trknl
holbuild build --trknl MyTheory
holbuild heap main
holbuild executable runtests
holbuild export -o build-output.hbx MyTheory
holbuild import build-output.hbx
holbuild clean MyTheory
holbuild gcWhen a command, target, heap or executable, or an execution-plan theory or
theorem isn't found, the error is followed by a line of up to three of the
nearest known names, nearest first, ignoring case:
holbuild: unknown build target: listLemasTheory
did you mean listLemmasTheory?
The line is left out when no name is close.
Global options go before the command, or before the target list when using the default build command:
holbuild -j4 MyTheory
holbuild --max-heap 4096 MyTheory
holbuild --source-dir /path/to/project MyTheory
holbuild --cache-dir /path/to/cache MyTheory
holbuild --remote-cache http://cache.example.org 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 --allow-cache-timeout-discrepancy --tactic-timeout 5 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.--remote-cache URL: optional Bazel-style HTTP remote cache endpoint. Also configurable withHOLBUILD_REMOTE_CACHE_URLor project-local.holconfig.toml.--json: emit newline-delimited JSON where supported.--quiet,--verbose,--verbosity LEVEL: adjust status output.-j N,-jN,--jobs N: build parallelism.--max-heap MB: Poly/ML maximum heap size for child HOL processes. Do not use--maxheap: holbuild is itself a Poly/ML program, and its runtime takes--maxheap MBas the cap on holbuild's own heap, so the option never reaches holbuild and child HOL processes run uncapped. holbuild rejects--maxheapwhere it can detect it (Linux), so there it cannot be used to cap holbuild's own heap either.--cooperate-dir DIR(orHOLBUILD_COOPERATE_DIR): share a machine memory budget with every holbuild process usingDIR; requires--max-heap. See below.--machine-max-mem MB: the budget to record inDIR/budgetwhen that file does not exist yet; a value that differs from the file at this process's first admission is an error.--prefetch-concurrency N: how many remote cache entries a build copies into the local cache at once, ahead of the build workers. Defaults to twice the build's jobs; only used with a remote cache.
-j limits children within one holbuild process. Independent processes on one
machine (several users, CI jobs, containers with a shared bind mount) can share
a memory budget instead: with --cooperate-dir DIR, every theory, generator and
heap-export child reserves its --max-heap plus 1 GiB, and starts only while
the reservations of all processes using DIR fit the byte count in
DIR/budget. A child that waits shows as (waiting for memory) in the status
line, as a waiting for machine memory line otherwise, and as a
waiting_for_memory event with --json. A child larger than the whole budget
fails at once.
This bounds admission; it does not enforce RSS, and it covers neither HOL
toolchain builds nor check, run or repl.
DIR/budgetis created on first use from--machine-max-mem, or elseMemTotalless a quarter (at least 1 GiB). To resize, replace it while holdingDIR/lock:flock DIR/lock sh -c 'echo BYTES > DIR/budget.new && mv DIR/budget.new DIR/budget'. Running children keep their reservations; new ones use the new value.- Each running child has a file
DIR/lease-*holding its byte charge,flocked by holbuild and inherited by the child, so a killed holbuild's reservation ends with its children, with no timeout. A lease nobody holds no longer counts and is removed by the next process that scansDIR. - For several users, create
DIRwritable by all of them (for example mode1777, or2775with a shared group) on a local filesystem. holbuild makes its files readable by everyone.
holbuild build --watch MyTheory runs an initial build, then watches project
inputs with inotifywait and rebuilds after changes. The directory of each
manifest in use is watched as well (not recursively), so a holbuild.toml or
.holconfig.toml appearing beside the manifest triggers a rebuild. Watch mode
currently requires inotifywait from inotify-tools and does not support
--json, --dry-run, or --repl-on-failure.
holbuild check MyTheory builds the theory if needed, loads it in HOL and
checks the tags of its stored theorems. It prints one line per
theorem whose tag carries an oracle other than DISK_THM (for instance
MyTheory.my_theorem: cheat) and one per axiom declared in the theory or its
ancestors outside HOL itself, that is, in the root package or a dependency
package (DepTheory.my_axiom: axiom). Theorems loaded from disk do not record
which axioms they use, so every such axiom in the ancestry is reported. With
findings it exits non-zero; otherwise it prints MyTheory: clean (N theorems)
and exits zero. holbuild check MyTheory:my_theorem checks one theorem.
The tags are those recorded in the built theory, which may have been restored
from a cache; run holbuild build --no-cache MyTheory first to check a fresh
build. HOL always starts from the toolchain's base state here, not from
[run].heap, which may hold an older copy of the theory.
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
the project manifest. Build output is written under .holbuild/ in the current
working directory.
holbuild looks for the manifest in the source directory and then in each
parent. In each directory it selects holbuild.toml if present, otherwise
holproject.toml, and moves to the parent only when neither exists. So the
nearest directory with a manifest wins, and within that directory
holbuild.toml wins; a nearer holproject.toml still beats an ancestor's
holbuild.toml. Only the selected file is parsed, with the same schema
whichever name it has. The files are never merged, and an invalid
holbuild.toml is an error even when a valid holproject.toml sits beside it.
Dependencies discovered on disk (git dependencies and .holconfig.toml path
overrides) use the same preference. An explicit shim manifest path is used as
declared. A nested directory containing either file is a separate project and
is excluded from its parent's automatic source discovery.
holbuild.toml exists so that a project can also carry a holproject.toml
written for another tool: Holmake reads holproject.toml with its own schema,
which holbuild's validator rejects. Such a project keeps that file for Holmake
and adds holbuild.toml for holbuild. This is a compatibility measure; the
shared-schema and workspace design questions of issue #110 are not settled by it.
[holbuild]
schema = 2
minimum_version = "<MAJOR.MINOR.PATCH>"[holbuild].minimum_version is an optional MAJOR.MINOR.PATCH version floor.
When present, the running holbuild version must be at least that version; when
omitted, holbuild performs no minimum-version check. The legacy schema = 2
marker is optional. Other schema values and required_version are rejected.
[project]
name = "example"project.name is required and identifies the logical package. A dependency
manifest's name must match its dependency key.
Holbuild parses committed semantics into separate metadata, source, entrypoint,
dependency, runtime, action-input, action-dependency, action-execution, and
generator layers. holbuild context reports canonical IDs for the package and
independently reusable policy layers; TOML formatting and table order do not
affect these IDs. It also reports typed package provenance: authoritative source
snapshot, definition and retrieval origins, logical/source roots, and implicit
HOL toolchain origin. Retrieval and materialization paths are excluded from
semantic package identities. Package declarations are resolved into one typed
project graph per kernel variant; context, source discovery, watching, and HOL
selection reuse its stable package instances rather than reparsing manifests.
Commands that need sources then run an explicit impure generator-preparation
phase before read-only source discovery; metadata-only context does not execute
generators. Source-requiring commands use one layered pipeline:
package definitions -> resolved package instances -> generator preparation
-> canonical source inventories -> package components -> resolution context
-> reachable source analysis -> resolved selected graph -> ordered build plan
-> action keys and execution
Canonical resolution-context, selected-graph, structural selected-plan, and combined resolved-plan identities exclude machine paths. Hashing and dependency extraction remain limited to the selected reachable frontier.
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, manifest path dependencies, 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", "gen"]
exclude = ["src/generated", "src/OneOff.sml"]
exclude_globs = ["*/selftest.sml", "*/examples/*"]
roots = ["src/MainScript.sml"]
root_groups = ["@generated"]
tactic_timeout = 10.0
[build.groups.generated]
include = ["gen/fixtures"]
include_globs = ["gen/*Script.sml"]
exclude = ["gen/fixtures/known-broken"]
exclude_globs = ["gen/*ExperimentalScript.sml"]
allow_empty = false
[build.theory_tactic_timeouts]
"SlowTheory" = 30.0
[build.root_tactic_timeouts]
"src/SlowScript.sml" = 60.0memberstellsholbuildwhere to discover source files. Membership makes a source available as a logical target and as a dependency of other targets, but does not by itself make the source part of the default build.excluderemoves concrete package-root-relative paths from discovery; a directory entry excludes its subtree, and a file entry excludes just that file.exclude_globsremoves package-root-relative glob matches from discovery. For backwards compatibility, glob patterns inexcludeare accepted with a deprecation warning and treated asexclude_globs; new manifests should use the explicit field.rootsare the default entry points whenholbuild buildis run with no target. Entries may be logical names (MainTheory), package-root-relative source paths, or@namebuild group references. Logical-name and source-path roots must name sources of this package discovered throughmembersand not removed byexcludeorexclude_globs; the.smlsuffix of a path may be omitted. If no package in the dependency graph declares roots or root groups,holbuild builddefaults to all discovered sources in the root project package only. Dependency packages, including the implicitholpackage, are not default-built merely because they have members. Useholbuild build --warn-unreachableto report discoverable theory scripts that are outside the root dependency closure.root_groupsadds build groups to the default build. Group names may use the uniform@nameform ("@generated") or the bare form ("generated").[build.groups.NAME]defines a build-system-only group;NAMEis the group name used by@NAME.includeandexcludeare concrete package-root-relative paths; a directory entry matches its whole subtree.include_globsandexclude_globsuse the same glob dialect asbuild.exclude_globs:*and?only,*crosses/, no**, and no character classes.allow_emptyis optional and defaults tofalse.@nameis the group-reference syntax (@followed by a group name) accepted byholbuild build @name,[build].roots,[build].root_groups,[[heap]].objects, and[[executable]].objects. It is not accepted in[actions.*].depsor[actions.*].loads, which remain real dependency/load edges.- A build group has phony-target semantics: it expands after generation and source discovery into ordinary logical targets, builds those targets only, and creates no aggregate HOL theory to load or export.
tactic_timeoutsets the default root-project proof-step timeout in seconds. The built-in default is2.5;0disables the timeout.theory_tactic_timeoutssets a timeout for exactly the named theory, given as a logical name (SlowTheory) or a package-root-relative script path (src/SlowScript.sml); it does not affect dependencies or consumers. Naming the same theory twice is an error.0disables the timeout for that theory. An explicit CLI--tactic-timeoutoverrides this table.root_tactic_timeoutslets individual root source files set timeout contracts for their dependency closures. Keys must spell the root exactly as it appears inroots(or, for a group root, as the member's package-root-relative path).
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 package-root-relative filesystem inputs that are hashed into the action key and copied into the action's stage directory at their path relative to the script, sodata/table.txtforsrc/FooScript.smlis read as../data/table.txt(the stage mirrors the package layout, so such copies stay inside it); absolute paths and..components are rejected. A directory or glob names the files under it, skipping any.holbuild/directory inside it.cache = falsedisables global cache restore/publish for the action.always_reexecute = truedisables local up-to-date skipping for the action.
Cache keys hash sources, not built outputs, so a cached theory is reusable only
if its build is reproducible: a deterministic script, with its inputs declared
and the same HOL toolchain, must produce a byte-identical Theory.dat in any
project directory. A script that reads the clock, environment variables,
undeclared files, or host paths must declare those files in extra_deps, or set
cache = false for what can't be declared. Builds stay correct either way,
because holbuild rejects a restored theory whose parents' .dat files differ
from the local ones, but descendants of a nondeterministic theory then miss the
cache wherever the local build of it differs from the one their cache entries
were built against, and their rebuilt entries can't be published.
Source files may also declare source-file-relative extra dependencies:
val () = holbuild_extra_deps ["../data/table.txt"];Theory scripts may declare deterministic extra output files:
val () = holbuild_extra_outputs ["results/test.nsv"];Each path is relative to the declaring source file and must be a fixed file path
inside the package: absolute paths, ./.. components, trailing slashes, and
glob syntax are rejected. The action must produce every declaration. Extra
outputs participate in up-to-date checks and cache publication/restoration, and
holbuild clean TARGET removes them. Holbuild records ownership lazily and
rejects another action that attempts to materialize the same path. As with
ordinary outputs, cleaning followed
by a normal build may restore them from cache; use existing no-cache or impure /
always-reexecute controls when source execution is required.
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 = []Declared outputs participate in source discovery as virtual entries. A generator
runs only when a selected source reaches one of its outputs, either as HOL/SML
source or through a manifest/inline extra_deps declaration. Its transitive
deps run first, and all declared outputs are then checked. Unreachable
generators do not require their command-line tools to be installed. Broad groups
and default roots still demand every generated output they select.
[[heap]]
name = "main"
output = "build/main.heap"
objects = ["MainTheory"]
[[executable]]
name = "runtests"
output = "tests/runtests.exe"
objects = ["runtests"]
main = "main"
[run]
heap = "build/main.heap"
loads = ["MyLib"]holbuild heap main builds the listed logical objects and uses HOL buildheap
to save the heap. [[heap]].objects may name theory and SML logical targets.
holbuild executable runtests builds the listed logical objects and uses HOL
buildheap --exe=<main> to produce an executable; main defaults to "main"
and executable objects may also include signature targets. Heap and executable
targets build their objects without proof steps, so no tactic timeout applies
and they reuse theories built by holbuild build --skip-proof-steps; use
holbuild build to check proof-step timeouts. Before starting HOL,
holbuild run and holbuild repl build every target named by [run].loads,
create an invocation-private project context under .holbuild/, and then load
the configured targets followed by user arguments.
By default, holbuild instruments modern theorem and explicit termination
proofs in the root package and executes them as proof steps. This gives better
failure locations, per-step tactic timeouts, and optional traces. Theorem proofs
also support failed-prefix checkpoints; termination proofs currently replay from
the preceding clean definition/theorem/dependency checkpoint. Theories in
dependency packages build without proof-step instrumentation or checkpoints;
to debug a dependency's proofs, build that package as the root project.
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:NAMEprints the proof-step plan for one theorem or explicit termination proof. If a theorem and terminating definition have the same name, the theorem takes precedence.--tactic-timeout SECONDSchanges the per-step timeout;0disables it. A step that times out fails the build even insideTRYor anORELSEalternative.--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. With no final context to save, holbuild does not eagerly reload the just-exported generated theory; dependents and explicit consumers load it normally through its manifest.
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. When checkpoints are enabled, the separate generated-theory load and
final-context save are recorded in final-context.log beside build.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, Theory.dat, and tracing-kernel Theory.tr.gz files by action
key. Cache hits materialise validated artefacts into the local .holbuild/
tree. Standard cache entries do not require a trace blob.
Completed theory artifacts are reused only when their recorded proof timeout
satisfies the requested timeout. --allow-cache-timeout-discrepancy waives that
check for project-local artifacts and all build-cache sources, including remote
and HBX-imported entries. It does not change source-execution watchdogs or
checkpoint compatibility. When this waiver permits reuse, project metadata
retains the artifact's recorded timeout, so a later build without the flag
applies the strict check.
A build can also consult and publish to one Bazel-style HTTP remote cache:
holbuild --remote-cache https://cache.example.org build MyTheory
HOLBUILD_REMOTE_CACHE_URL=https://cache.example.org holbuild build MyTheory
# or in project-local .holconfig.toml:
# [remote_cache]
# url = "https://cache.example.org"Remote cache misses or errors fall back to the local cache/source build path.
While a build runs, --prefetch-concurrency background threads (twice -j by
default) also copy the remote entries of the plan's out-of-date theories into
the local cache in build order, so a theory's restore usually finds its entry
already local. This prefetch is best-effort and silent; whatever it misses is
fetched when the theory is reached, as before. HOLBUILD_CACHE_TRACE=1 shows
the prefetch thread count as remote cache prefetch threads: <n> and each
prefetched entry as remote cache prefetched: <key>.
A missing HOL toolchain is restored from the same remote cache when its HOL
commit, build arguments, installation path (with symlinks resolved, as HOL's
build records it), platform, C library, Poly/ML runtime (release and
architecture variant, as poly -v reports them) and resolved poly
executable path match. The source-analysis helper is not archived; holbuild
rebuilds it against the restored toolchain. After a remote miss, a successful
local toolchain build is published automatically unless another builder
published it meanwhile; an archive that is missing its blob or fails to extract
or validate is replaced by the local build (a failed download is not). Local and remote hits are not re-uploaded, and publication
failure is only a warning.
The remote endpoint stores content blobs under /cas/<sha256> and holbuild
cache metadata under /ac/<action-key>. CAS transfers use zstd compression when
supported by the server; action metadata stays small and is sent uncompressed.
This is a live accelerator, not remote execution and not an immutable release
registry. Remote cache contents are trusted build inputs: cache hits can load
Poly/ML objects, and restored toolchains execute cached binaries. Use an
authenticated HTTPS endpoint with trusted writers. Content hashes detect
corruption but do not authenticate the cache publisher.
holbuild talks to the remote cache through libcurl (7.66 or later, with an
asynchronous DNS resolver), loaded at run time: libcurl.so.4 on Linux, the
system /usr/lib/libcurl.4.dylib on macOS. If it can't be loaded or lacks
those, holbuild says why once and runs the curl command for each request
instead, as it did before it used libcurl. As with the curl command,
CURL_CA_BUNDLE, SSL_CERT_FILE and SSL_CERT_DIR select the CA certificates
that HTTPS connections trust.
For private caches, put credentials in local/CI configuration, not in
the project manifest. CLI --remote-cache overrides HOLBUILD_REMOTE_CACHE_URL,
which overrides .holconfig.toml's [remote_cache].url. bazel-remote
supports HTTP Basic Auth; the safest holbuild path is a credentials file in
curl's config syntax:
cat > "$RUNNER_TEMP/holbuild-remote-cache.curl" <<'EOF'
user = "build-user:secret-password"
EOF
chmod 600 "$RUNNER_TEMP/holbuild-remote-cache.curl"
HOLBUILD_REMOTE_CACHE_CURL_CONFIG="$RUNNER_TEMP/holbuild-remote-cache.curl" \
holbuild --remote-cache https://cache.example.org build MyTheoryHOLBUILD_REMOTE_CACHE_CURL_CONFIG overrides .holconfig.toml's
[remote_cache].curl_config. Through libcurl, holbuild reads only the user
option from this file (user = "name:password", user name:password or
--user name:password; # starts a comment line). A file with any other
option (proxy, cacert, netrc, ...) makes holbuild say so once and use the
curl command, which reads the whole file.
The URL form https://user:password@cache.example.org also works, but can
expose credentials via process listings or shell history; prefer the config
file form for CI and shared machines. holbuild redacts URL userinfo in its own
remote-cache diagnostics, but it cannot hide secrets from the operating system if
they are passed as command-line arguments.
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. Import HBX archives
only from trusted publishers because those cached build outputs may later be
loaded by Poly/ML.
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-only
holbuild gc --toolchains-onlyLong cache GC stages report scanned and removed counts on stderr while they
run, unless --quiet is given. Progress never goes to stdout, which keeps the
project clean: removed ... and cache gc: removed ... summaries.
gc --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.
Cache GC also removes installed HOL toolchains under hol-toolchains/ that no
holbuild command has used within the retention period; a removed toolchain is
rebuilt, or restored from a remote cache, the next time a project needs it.
Toolchains being built, restored, or used by a running holbuild process
(including build --watch) are skipped. Project checkpoints saved against a
removed toolchain are discarded and rebuilt automatically, but heaps exported
with holbuild heap or holbuild executable must be re-exported before they
are used outside holbuild.
gc --toolchains-only removes only such stale toolchains, skipping project
cleanup and the action/blob scan, which can take minutes on a large cache. Like
--cache-only, it needs no project or HOL toolchain. --clean-only,
--cache-only, and --toolchains-only are mutually exclusive.
--max-checkpoints-gb applies only to project cleanup and is ignored with
--cache-only or --toolchains-only.
Local machine settings may go in .holconfig.toml:
[build]
jobs = 16
checkpoint_limit_gb = 20
exclude = ["worktrees"]
exclude_globs = ["scratch/*"]
tactic_timeout = 10.0
[remote_cache]
url = "https://cache.example.org"
curl_config = "/path/to/holbuild-remote-cache.curl"
[overrides.foo]
path = "../foo"
[overrides.bar]
git = "$BAR_REPO"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. Local exclude and exclude_globs apply only to the root package.
Dependency discovery is controlled by each dependency's committed manifest.
[overrides.NAME] maps a declared dependency to local workstation source. Use
path to read an existing checkout directly, or git to replace the dependency
git source while still checking out the manifest's exact rev. Override values
may use environment variables. Local relative paths are resolved from the project
root; URL-like git remotes are left unchanged. Overrides apply during recursive
dependency resolution too, so a dependency declared by another dependency can be
supplied from disk. Overridden dependencies still need a matching project.name
in their manifest; dependencies.hol cannot be overridden this way.
A from/path/manifest shim cannot be overridden directly; override the direct
source dependency named by from instead.
Unknown fields in recognised manifest 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, HOL build arguments, Poly/ML runtime and resolvedpolypath.
Repository tests resolve the schema 2 HOL toolchain cache automatically:
make testTo reuse an explicit checkout instead, pass HOLDIR=/path/to/built/HOL. By
default the checkout must be at the revision recorded in vendor/hol/REV.
To test against another immutable HOL commit without changing the vendor pin,
leave HOLDIR unset and override the test revision. The runner provisions and
reuses the corresponding revision-keyed toolchain through holbuild buildhol,
and generated test manifests use the same revision:
env -u HOLDIR -u HOLBUILD_HOLDIR \
HOLBUILD_TEST_HOL_REV=<full-40-character-commit> make testMaintainer 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.