Skip to content

ocx-sh/rules_ocx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

rules_ocx

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.

Quick start

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

How it works

  1. The ocx extension creates @ocx_tool by downloading the pinned ocx release listed in the vendored dist/dist.json snapshot of https://setup.ocx.sh/dist.json (sha256-verified).
  2. 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).
  3. Packages live in the shared OCX_HOME store (~/.ocx by 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.
  4. Generated repos contain launcher scripts (skylib native_binary) that apply the package environment and exec the store binaries — usable in tools =, $(location …), sh_test env, and bazel run.

Corporate mirrors

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.

Reproducibility

  • 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 :latest stays reproducible until you refresh the snapshot. OCX's native tag locking, works across all platforms.
    • pins = {"linux/amd64": "sha256:…"} — explicit per-platform manifest digests (reported by ocx 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_HOME store paths (the nixpkgs model). Use isolated_home = True to keep a store per repository if you need stricter isolation.

Lazy provisioning

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).

API docs

Generated by stardoc into docs/. Regenerate with bazel run //docs:update.

Examples

License

Apache-2.0. See LICENSE.

About

No description, website, or topics provided.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors