diff --git a/README.md b/README.md
index d5d7ade..eadf4a1 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ See [toolshed/README.md](toolshed/README.md) for build instructions and
| tag | Python | GCC (native) | GCC (arm-none-eabi) | Clang (native) | Cmake | Host Platforms | Other Utilities |
|----------|--------|--------------|---------------------|----------------|-------|----------------|-----------------|
+| [ts26.4.3](https://github.com/OpenCyphal/docker_toolchains/pkgs/container/toolshed) | 3.14 | 15.2.0 | 14.2.rel1 | 22.1.2 | 4.2.3 |
| - Rust 1.93.1
- Go 1.26
- Node.js 22.22.1
- TypeScript 5.2.2
- Emscripten SDK 6.0.2
- LLVM 22.1.2
- MLIR 22.1.2
- JLink V816
- cppcheck
- yakut
- libpcap0.8-dev
- network-tools
- can-utils
- doxygen 1.13.2
- tox
- nox
- gcovr
- lit
- Dafny 4.11.0
- .NET SDK 10.0
- z3 4.13.3
- Bazel 9.2.0 (bazelisk 1.29.0)
- gcc-multilib (amd64 only)
|
| [ts26.4.2](https://github.com/OpenCyphal/docker_toolchains/pkgs/container/toolshed) | 3.14 | 15.2.0 | 14.2.rel1 | 22.1.2 | 4.2.3 | | - Rust 1.93.1
- Go 1.26
- Node.js 22.22.1
- TypeScript 5.2.2
- Emscripten SDK 6.0.2
- LLVM 22.1.2
- MLIR 22.1.2
- JLink V816
- cppcheck
- yakut
- libpcap0.8-dev
- network-tools
- can-utils
- doxygen 1.13.2
- tox
- nox
- gcovr
- lit
- Dafny 4.11.0
- .NET SDK 10.0
- z3 4.13.3
- gcc-multilib (amd64 only)
|
| [ts26.4.1](https://github.com/OpenCyphal/docker_toolchains/pkgs/container/toolshed) | 3.14 | 15.2.0 | 14.2.rel1 | 22.1.2 | 4.2.3 | | - Rust 1.93.1
- Go 1.26
- Node.js 22.22.1
- TypeScript 5.2.2
- Emscripten SDK 6.0.2
- LLVM 22.1.2
- MLIR 22.1.2
- JLink V816
- cppcheck
- yakut
- libpcap0.8-dev
- network-tools
- can-utils
- doxygen 1.13.2
- tox
- nox
- gcovr
- lit
- gcc-multilib (amd64 only)
|
| [ts24.4.3](https://github.com/OpenCyphal/docker_toolchains/pkgs/container/toolshed) | | - 13.3.0
- 12.3.0
- 11.4.0
- 10.5.0
- 7.5.0
| 13.3.1 | | 3.31.5 | | - JLink V816
- cppcheck-2.13
- yakut 0.13.0
- libpcap0.8-dev
- network-tools
- can-utils
- doxygen 1.13.2
- tox
- nox
- govr
- gcc-multilib (amd64 only)
|
@@ -48,7 +49,7 @@ You can use this in your workflow yaml like this:
jobs:
my-job:
runs-on: ubuntu-latest
- container: ghcr.io/opencyphal/toolshed:ts26.4.2
+ container: ghcr.io/opencyphal/toolshed:ts26.4.3
```
diff --git a/toolshed/CHANGELOG.md b/toolshed/CHANGELOG.md
index 43af7ba..0bf8be5 100644
--- a/toolshed/CHANGELOG.md
+++ b/toolshed/CHANGELOG.md
@@ -3,6 +3,58 @@
Per-release notes for the `ghcr.io/opencyphal/toolshed` image. The summary table of
every published tag lives in the [top-level README](../README.md#opencyphaltoolshedts).
+## ts26.4.3
+
+Adds:
+
+- Bazel, via bazelisk 1.29.0, on both `linux/amd64` and `linux/arm64`
+- Bazel 9.2.0, unpacked into the image rather than fetched on first use
+
+`bazel` on `PATH` is bazelisk, not a Bazel release binary. Bazel's own apt
+repository publishes `amd64` only while this image ships both architectures — the
+same constraint that decided how Dafny is installed in ts26.4.2 — and bazelisk
+publishes both. Installing the launcher rather than a fixed binary is also what
+lets a project pin its own Bazel through `.bazelversion`.
+
+`BAZELISK_HOME` is `/opt/bazelisk`, world-readable, rather than the default under
+`$HOME`. The default is invisible to any other user the container runs as, which
+CI routinely does; this is the trap `dotnet tool install --tool-path` avoids for
+Dafny.
+
+**`USE_BAZEL_VERSION` is deliberately not set.** It takes precedence over a
+project's `.bazelversion` — measured, not assumed — so setting it here would
+silently build every project with 9.2.0 whatever that project pinned. A project
+that wants the pre-warmed toolchain writes `9.2.0` into its own `.bazelversion`,
+which is where that decision belongs; a project pinning anything else downloads it
+on first use.
+
+### Bazel builds in this image are not offline
+
+Worth stating plainly, because it is the first thing anyone reaching for Bazel
+will assume otherwise.
+
+Bazel's hermeticity guarantees cover action *execution* — sandboxed actions,
+declared inputs, no leakage from the host. They have never covered the fetch
+phase. Under bzlmod that phase is unavoidable: a module declaring **no**
+dependencies at all still resolves `bazel_tools`' transitive deps
+(`apple_support`, `protobuf`, `rules_java`, `rules_python`, `platforms`,
+`rules_cc`, `bazel_skylib`, and more) from `bcr.bazel.build` before analysis
+begins. `--noenable_bzlmod` does not escape it on Bazel 9.
+
+None of the obvious mitigations move this, all measured against a cold cache:
+a warmed `--repository_cache` holds archives rather than registry metadata; a
+committed `MODULE.bazel.lock` records the resolution but still fetches the module
+files; and `bazel vendor --vendor_dir` does not cover registry access either. What
+does work is a warm per-user `$HOME/.cache/bazel`, which is no help to CI running
+as an arbitrary uid.
+
+A project that needs offline or air-gapped builds points Bazel at its own registry
+mirror (`--registry=file:///...`) or vendors into its own workspace. Those are
+per-project decisions — which modules you need is a function of your dependency
+graph — so this image deliberately does not pin `--registry`, ship a partial
+mirror that would go stale at every Bazel bump, or otherwise get in the way. It
+supplies the toolchain; the dependency policy stays with the project.
+
## ts26.4.2
Adds:
diff --git a/toolshed/Dockerfile b/toolshed/Dockerfile
index 7119925..83d2272 100644
--- a/toolshed/Dockerfile
+++ b/toolshed/Dockerfile
@@ -271,6 +271,83 @@ RUN printf 'method Smoke(x: int) requires x > 0 ensures x + 1 > 1 { }\n' > /tmp/
&& dafny verify /tmp/smoke.dfy \
&& rm /tmp/smoke.dfy
-# +---------------------------------------------------------------------------+# LEAVE THE WORKDIR AS /repo
+# +---------------------------------------------------------------------------+
+# | INSTALL BAZEL
+# +---------------------------------------------------------------------------+
+# bazelisk rather than a Bazel release binary, and from GitHub rather than from
+# apt. Bazel's own apt repository publishes amd64 only, and this image ships both
+# architectures -- the same constraint that decided Dafny above. bazelisk
+# publishes linux/amd64 and linux/arm64, so it is the one route that serves both.
+#
+# Installing bazelisk as `bazel` is also what lets a project pin its own Bazel:
+# bazelisk reads .bazelversion from the workspace root and fetches that version.
+FROM dafny AS bazel
+WORKDIR /tmp
+COPY cached-download.sh .
+
+ARG BAZELISK_VERSION=1.29.0
+# The version pre-warmed into the image below. A project pinning this one gets it
+# without a download; any other version is fetched on first use.
+ARG BAZEL_VERSION=9.2.0
+
+# BAZELISK_HOME defaults to $HOME/.cache/bazelisk, which is invisible to any other
+# user the container runs as -- and CI routinely runs as one. A shared path, made
+# world-readable below, is what lets all of them use the pre-warmed Bazel. This is
+# the same trap `dotnet tool install --tool-path` avoids for Dafny.
+ENV BAZELISK_HOME=/opt/bazelisk
+
+ARG BAZELISK_CHECKSUM_FILE=bazelisk-${BAZELISK_VERSION}-linux-${TARGETARCH}.sha256
+COPY ${BAZELISK_CHECKSUM_FILE} .
+RUN --mount=type=cache,target=/bazelisk-download \
+ ./cached-download.sh \
+ --cache-target "/bazelisk-download" \
+ --download-url "https://github.com/bazelbuild/bazelisk/releases/download/v${BAZELISK_VERSION}/bazelisk-linux-${TARGETARCH}" \
+ --download-to-file "/bazelisk-download/bazelisk-linux-${TARGETARCH}" \
+ --checksums "/tmp/${BAZELISK_CHECKSUM_FILE}" \
+ --algorithm 256 \
+ && install -m 0755 "/bazelisk-download/bazelisk-linux-${TARGETARCH}" /usr/local/bin/bazel
+
+# Bazel itself is unpacked into BAZELISK_HOME here rather than on first use. The
+# throwaway workspace is what makes that happen: bazelisk honours .bazelversion
+# only at a workspace root, and with no workspace it resolves "latest" over the
+# network instead.
+#
+# Deliberately NOT setting USE_BAZEL_VERSION. That variable takes precedence over
+# a project's .bazelversion -- measured, not assumed -- so an image setting it
+# would silently build every project with this version whatever the project
+# pinned. A project that wants the pre-warmed Bazel commits `9.2.0` to its own
+# .bazelversion, which is where that decision belongs.
+RUN mkdir -p /tmp/bazel-warm \
+ && cd /tmp/bazel-warm \
+ && printf 'module(name = "warm", version = "0.0.1")\n' > MODULE.bazel \
+ && touch BUILD.bazel \
+ && echo "${BAZEL_VERSION}" > .bazelversion \
+ && bazel --version \
+ && bazel shutdown \
+ && cd /tmp \
+ && rm -rf /tmp/bazel-warm \
+ && chmod -R a+rwX "${BAZELISK_HOME}" \
+ && chmod 1777 "${BAZELISK_HOME}"
+
+# Build something rather than just running --version. Launching proves bazelisk
+# resolved a binary; building additionally proves that binary can execute an
+# action and write an output, which is the part that silently goes missing.
+#
+# This step needs network, and so will any real Bazel build: Bazel resolves even
+# its built-in module dependencies through the Bazel Central Registry, exactly as
+# npm, cargo, and pip in this image fetch theirs. The image supplies the
+# toolchain, not a hermetic dependency cache.
+RUN mkdir -p /tmp/bazel-smoke \
+ && cd /tmp/bazel-smoke \
+ && printf 'module(name = "smoke", version = "0.0.1")\n' > MODULE.bazel \
+ && printf 'genrule(name = "g", outs = ["o.txt"], cmd = "echo built > $@")\n' > BUILD.bazel \
+ && echo "${BAZEL_VERSION}" > .bazelversion \
+ && bazel build //:g \
+ && grep -q built bazel-bin/o.txt \
+ && bazel shutdown \
+ && cd /tmp \
+ && rm -rf /tmp/bazel-smoke
+
+# +---------------------------------------------------------------------------+
# LEAVE THE WORKDIR AS /repo
WORKDIR /repo
diff --git a/toolshed/bazelisk-1.29.0-linux-amd64.sha256 b/toolshed/bazelisk-1.29.0-linux-amd64.sha256
new file mode 100644
index 0000000..60a7b98
--- /dev/null
+++ b/toolshed/bazelisk-1.29.0-linux-amd64.sha256
@@ -0,0 +1 @@
+5a408715e932c0250d28bd84555f12edbf70117de42f9181691c736eacc4a992 bazelisk-linux-amd64
diff --git a/toolshed/bazelisk-1.29.0-linux-arm64.sha256 b/toolshed/bazelisk-1.29.0-linux-arm64.sha256
new file mode 100644
index 0000000..791be5a
--- /dev/null
+++ b/toolshed/bazelisk-1.29.0-linux-arm64.sha256
@@ -0,0 +1 @@
+e20e8b0f4f240091b7a55bf17b9398bd4f40ee70ae0208dff95dd4c445fb4010 bazelisk-linux-arm64