Bazel module extension that provisions development tools through
OCX — the OCI-backed package manager. Bootstrap the pinned
ocx CLI inside a repository rule, then let it resolve, download, and compose
tool packages; consume them as ordinary runnable Bazel targets.
rules_ocx deliberately never re-implements OCX internals in Starlark. All
resolution goes through the ocx binary; the durable contracts are
ocx.lock digests and the OCI manifests.
# MODULE.bazel
bazel_dep(name = "rules_ocx", version = "0.1.0")
ocx = use_extension("@rules_ocx//ocx:extensions.bzl", "ocx")
# Flagship: provision the workspace toolchain from ocx.toml + ocx.lock.
ocx.project(
name = "tools",
ocx_toml = "//:ocx.toml",
ocx_lock = "//:ocx.lock",
)
# Ad-hoc: a single package, tag-floating or digest-pinned.
ocx.package(
name = "jq",
package = "ocx.sh/jq:latest",
)
use_repo(ocx, "jq", "tools")# BUILD.bazel
genrule(
name = "pretty",
srcs = ["data.json"],
outs = ["pretty.json"],
cmd = "$(location @jq//:jq) . $< > $@",
tools = ["@jq//:jq"],
)
sh_test(
name = "lint",
srcs = ["lint_test.sh"],
data = ["@tools//:shellcheck"],
env = {"SHELLCHECK_BIN": "$(location @tools//:shellcheck)"},
)Every tool declared in ocx.toml (default group) appears as a runnable
target @tools//:<name>; every entrypoint of an ocx.package() appears as
@<name>//:<entrypoint>.
- The
ocxextension creates@ocx_toolby downloading the pinned ocx release listed in the vendoreddist/dist.jsonsnapshot ofhttps://setup.ocx.sh/dist.json(sha256-verified). ocx.project()/ocx.package()repository rules shell out to that binary:ocx lock --check(staleness gate),ocx pull/ocx package install(populate the content-addressed store),ocx --format json env(composed environment).- Packages live in the shared
OCX_HOMEstore (~/.ocxby default) — content-addressed, digest-pinned, hardlink-composed. Repository rules run unsandboxed, so the store is shared with your shell, direnv, and CI, fetched once per machine. - Generated repos contain launcher scripts (skylib
native_binary) that apply the package environment and exec the store binaries — usable intools =,$(location …),sh_testenv, andbazel run.
Same knobs as the setup.ocx.sh installer, honored by the repository rules:
| Env var | Effect |
|---|---|
OCX_INSTALL_DIST_URL |
Fetch the release manifest from your mirror instead of the vendored snapshot. |
OCX_INSTALL_MIRROR_URL |
Rewrite the ocx binary download to <mirror>/<tag>/<filename>. The manifest sha256 is still enforced — a mirror can move bytes, not change them. |
OCX_MIRRORS |
JSON map {"ocx.sh": "https://mirror.corp/ocx"} — package pulls go to the mirror; ocx.lock digests stay keyed to the upstream host, so lockfiles are portable. |
OCX_INSECURE_REGISTRIES |
Allow plain-HTTP mirrors (comma list). |
OCX_AUTH_<REGISTRY>_{TYPE,USER,TOKEN} |
Registry credentials (also: docker config). Not enumerable by Bazel — run bazel fetch --force after changing auth. |
Passed through to repo rules as well: OCX_HOME, OCX_INDEX, OCX_OFFLINE,
OCX_FROZEN, OCX_REMOTE, OCX_JOBS, OCX_DEFAULT_REGISTRY.
- Project tier is fully pinned by your committed
ocx.lock(per-platform sha256 digests). A stale lock fails the fetch with instructions. - Package tier, pick one:
index = "//:index"— commit an index snapshot (ocx --index index index update ocx.sh/jq); tags resolve frozen from it, so:lateststays reproducible until you refresh the snapshot. OCX's native tag locking, works across allplatforms.pins = {"linux/amd64": "sha256:…"}— explicit per-platform manifest digests (reported byocx package install -p <platform>).- Neither: floating tags resolve at fetch time; the fetch log prints the resolved digest.
- Remote execution is a non-goal for now: launchers reference absolute
OCX_HOMEstore paths (the nixpkgs model). Useisolated_home = Trueto keep a store per repository if you need stricter isolation.
Add bins = [...] to ocx.project() or ocx.package() and nothing is
pulled at fetch time: each name becomes a launcher that re-enters
ocx run / ocx package exec, materializing content on its first
execution. Tool content never becomes a Bazel action input — actions key on
the lockfile (project) or the digest-pinned reference (package, so pins
or @sha256: is required) — and the POSIX launchers resolve everything
through runfiles, so the keys are identical across machines. The result: a
fully remote-cached build downloads no tool content at all, even on a
pristine machine. The first cache-miss action on a machine pays the pull
once, into the shared content-addressed store.
Trade-offs: bins names are not validated at fetch time, //:content /
//:env.bzl are unavailable (they would need materialized bytes), and the
first executions on a cold machine race on the store
(ocx#179).
Generated by stardoc into docs/. Regenerate with
bazel run //docs:update.
examples/project— workspace toolchain from ocx.toml/lock, eager + lazyexamples/package— ad-hoc packages: floating, frozen-index, digest-pinned, lazyexamples/cross_platform— per-platform repos + transitions
Apache-2.0. See LICENSE.