Toolchains¶
toolchains
¶
Toolchain catalog and host-side provisioning for sandbox profiles.
A sandbox profile's toolchains map names an entry in :data:CATALOG to a
version: exact (1.26.5), partial (22, 1.26) where the tool's
official index lists releases without an API token, or auto, read from
repo files (go.mod, package.json, .nvmrc, .node-version,
.python-version, rust-toolchain, rust-toolchain.toml,
.tool-versions).
Everything here runs on the host and never executes anything from the repo or from an archive:
- :func:
resolveturns the requested version into an exact release, the archive URL on the tool's fixed official host and the digest published by the tool's official checksum source (a vendored table for protoc versions without one). Repo files are read as data, with a size cap, and a symlink that leads out of the workdir is refused. - :func:
fetch_archivedownloads the archive (size and time limits, HTTPS only, redirects only to the entry's own hosts and, on GitHub, its own release path), verifies it and stores it in the host cache (~/.cache/agentic-ci/toolchains/, keyed by sha256, atomic writes). Nothing unverified is ever kept or installed. - :func:
provisiondoes both for every toolchain of a profile and hands each verified archive to a backend :class:Installer, which extracts it inside the sandbox; one the installer already has is skipped without a download when its official digest is a sha256. A failure is recorded in the toolchain's :class:ToolchainResultand that toolchain is skipped; the others go on.
:class:ToolchainEnv holds the variables a provisioned toolchain needs
(PATH prepends, GOTOOLCHAIN=local, Go and npm caches, the pnpm
store, CARGO_HOME), all under /sandbox/.local or /sandbox/.cache so they stay out
of the workdir that is downloaded back.
Messages never carry repo-controlled text: they are fixed strings plus validated versions and file names chosen here. Details (exception classes, HTTP hosts) go to the job log.
VERSION_RE = re.compile('[0-9]{1,9}(\\.[0-9]{1,9}){0,2}')
module-attribute
¶
A toolchain version (use fullmatch): MAJOR[.MINOR[.PATCH]], at most 9 digits each.
The bound keeps a repo file from putting an arbitrarily long number into URLs, results and messages.
SANDBOX_TOOLCHAIN_ROOT = '/sandbox/.local/toolchains'
module-attribute
¶
Where toolchains are extracted inside the sandbox, one <name>-<version> dir each.
CACHE_ENV = 'AGENTIC_CI_TOOLCHAIN_CACHE'
module-attribute
¶
Environment variable that moves the host cache (default ~/.cache/agentic-ci/toolchains).
PROTOC_SHA256 = MappingProxyType({'protoc-28.3-linux-x86_64.zip': '0ad949f04a6a174da83cdcbdb36dee0a4925272a5b6d83f79a6bf9852076d53f', 'protoc-28.3-linux-aarch_64.zip': '1de522032a8b194002fe35cab86d747848238b5e4de4f99648372079f5b46f9a', 'protoc-29.5-linux-x86_64.zip': 'a3f094363cd205c6f7af0d1b9305cb4c8517043f265cdb188f098cae93e8b217', 'protoc-29.5-linux-aarch_64.zip': '25eb0848ff13a90a0b2d2a3b4a9d2babc7fbfe158f596c00d8c26a21028dd6f5', 'protoc-30.2-linux-x86_64.zip': '327e9397c6fb3ea2a542513a3221334c6f76f7aa524a7d2561142b67b312a01f', 'protoc-30.2-linux-aarch_64.zip': 'a3173ea338ef91b1605b88c4f8120d6c8ccf36f744d9081991d595d0d4352996', 'protoc-31.1-linux-x86_64.zip': '96553041f1a91ea0efee963cb16f462f5985b4d65365f3907414c360044d8065', 'protoc-31.1-linux-aarch_64.zip': '6c554de11cea04c56ebf8e45b54434019b1cd85223d4bbd25c282425e306ecc2', 'protoc-32.1-linux-x86_64.zip': 'e9c129c176bb7df02546c4cd6185126ca53c89e7d2f09511e209319704b5dd7e', 'protoc-32.1-linux-aarch_64.zip': '4a802ed23d70f7bad7eb19e5a3e724b3aa967250d572cadfd537c1ba939aee6a', 'protoc-33.0-linux-x86_64.zip': 'd99c011b799e9e412064244f0be417e5d76c9b6ace13a2ac735330fa7d57ad8f', 'protoc-33.0-linux-aarch_64.zip': '4b96bc91f8b54d829b8c3ca2207ff1ceb774843321e4fa5a68502faece584272', 'protoc-33.6-linux-x86_64.zip': '9c49962391ff8b5754342509efc3885db91dfe11a358e1e98c9a71518f0ad199', 'protoc-33.6-linux-aarch_64.zip': '4016cd2ad24c6a2405332a2ef7e04b719240e40d811587c6a1c4bbe514cd668c', 'protoc-34.1-linux-x86_64.zip': 'af27ea66cd26938fe48587804ca7d4817457a08350021a1c6e23a27ccc8c6904', 'protoc-34.1-linux-aarch_64.zip': '31c5e9e3c7bf013cf41fb97765ee255c140024a6b175b6cc9b64beddd7c23ba7', 'protoc-35.1-linux-x86_64.zip': '6930ebf62bd4ea607b98fff052596c6ee564b9835b4ce172c75a3f53ae9d91b7', 'protoc-35.1-linux-aarch_64.zip': '01bf9d08808c7f96678b63f4bd8efa559bb4f83d5a7a270d5edaf507f9d5d9cf'})
module-attribute
¶
Vendored, cross-checked sha256 of protoc releases that predate protobuf's checksum manifest.
PYTHON_BUILD_TAGS = MappingProxyType({version: tag for tag, versions in _PYTHON_BUILDS.items() for version in versions.split()})
module-attribute
¶
python-build-standalone release tag by exact CPython version, for pins the newest lacks.
CATALOG = MappingProxyType({e.name: e for e in _ENTRIES})
module-attribute
¶
Every toolchain a sandbox profile may request, by name.
ToolchainError
¶
Bases: Exception
A toolchain could not be resolved, downloaded, verified or installed.
The message is a fixed string plus validated versions and file names, so
it can be shown to users and recorded as a result's reason.
CatalogEntry(name, url, hosts, source, bin_dirs, arch, archive='tar.gz', checksum_url='', binary='', node_scripts=dict(), components=(), component_root='', partial=False, auto='', min_parts=3, max_parts=3)
dataclass
¶
How to fetch and verify one toolchain.
url is the archive URL template ({version}, {arch} and, for
python, {tag}) on a fixed official host; its last path segment,
URL-decoded, is the file name the checksum source lists. hosts is
every host the entry may contact, redirects included. source names
the resolver and checksum source (see :func:resolve). bin_dirs are
the directories, relative to the extracted toolchain, prepended to
PATH. archive is tar.gz, tar.xz, zip or binary (a
single executable stored as <WRAPPER_DIR>/<binary>). components
(with {arch}) names the directories under the archive's
component_root (with {version} and {arch}) that are merged
into the toolchain directory, as the tool's own installer would; the rest
of the archive is not installed. node_scripts maps a
launcher name to a script in the archive that the launcher runs with
node. arch maps the host machine (x86_64, aarch64) to the
name the tool uses. partial says whether a partial version resolves.
auto names the repo files auto reads (empty: auto is an
error). min_parts is the fewest version parts an exact version has,
max_parts the most a version may have.
path_prefix
property
¶
/<owner>/<repo>/releases/ for an entry on github.com, else "".
Every github.com URL the entry fetches, redirects included, must start with it, so a renamed or transferred repo cannot supply the archive.
Fetcher
¶
Bases: Protocol
Streams an HTTPS resource; the tests replace the real one.
stream(url, *, hosts, limit, timeout, headers=None, path_prefix='')
¶
Yield the body of url in chunks.
Raises :class:ToolchainError when url or a redirect breaks
:func:_check_url (with hosts and path_prefix), the status is
not 200, the body exceeds limit bytes or the whole transfer takes
longer than timeout seconds.
Source code in src/agentic_ci/toolchains.py
HttpFetcher(session=None)
¶
:class:Fetcher over requests, following redirects only within hosts.
Source code in src/agentic_ci/toolchains.py
Resolved(name, version, url, filename, algorithm, digest, size=None)
dataclass
¶
An exact release and the digest its official checksum source publishes.
digest
instance-attribute
¶
Hex digest of the archive in :attr:algorithm (sha256 or sha512).
AutoVersion(version, source, sha512=None)
dataclass
¶
InstallPlan(name, version, sha256, archive, target, binary='', node_scripts=dict(), components=(), component_root='')
dataclass
¶
Installer
¶
ToolchainEnv(path=(), variables=dict())
dataclass
¶
Variables for the provisioned toolchains, for the env script and the setup shim.
path is prepended to PATH in order; variables are exported
as they are.
script_lines()
¶
export lines for a bash script.
Source code in src/agentic_ci/toolchains.py
ToolchainResult(name, requested, resolved='', sha256='', status='failed', reason='')
dataclass
¶
One toolchain's outcome, as recorded in _run/toolchains.json.
status is installed, present (a reused sandbox already had
it) or failed; reason says why it failed.
Provisioned(results, env)
dataclass
¶
Result of :func:provision.
host_machine()
¶
The sandbox's machine (the host's, OpenShell runs it natively): x86_64 or aarch64.
Raises :class:ToolchainError on any other machine.
Source code in src/agentic_ci/toolchains.py
auto_version(name, workdir)
¶
Read the version auto means for toolchain name from repo files in workdir.
Source code in src/agentic_ci/toolchains.py
resolve(name, requested, *, workdir, fetcher, arch=None)
¶
Resolve requested (a version or auto) for catalog entry name.
Returns the release and, for auto, what was read from the repo.
Raises :class:ToolchainError.
Source code in src/agentic_ci/toolchains.py
default_cache_dir()
¶
$AGENTIC_CI_TOOLCHAIN_CACHE, else ~/.cache/agentic-ci/toolchains.
Source code in src/agentic_ci/toolchains.py
write_atomic(path, data)
¶
Write data to path atomically, through a fresh mkstemp name in its directory.
Nothing left at another name in the directory (a symlink, say) is written through; path itself is replaced, never followed.
Source code in src/agentic_ci/toolchains.py
fetch_archive(resolved, *, fetcher, cache_dir)
¶
Return the verified archive for resolved from the host cache, downloading it if needed.
The archive is written to a temporary file and renamed to
<cache_dir>/<sha256> only once its digest matches the official one;
a mismatch deletes it and raises :class:ToolchainError. A digest other
than sha256 (pnpm's sha512) gets an alias file <algorithm>/<hex>
holding the sha256. Returns the path and the archive's sha256.
Source code in src/agentic_ci/toolchains.py
toolchain_env(installed)
¶
The :class:ToolchainEnv for installed toolchains (in the given order).
Source code in src/agentic_ci/toolchains.py
provision(toolchains, *, workdir, installer, fetcher=None, cache_dir=None)
¶
Resolve, fetch, verify and install every toolchain in toolchains.
A toolchain that fails is recorded as failed and skipped; the run goes
on. Names outside the catalog and versions that are neither auto nor
digits are skipped the same way (parse_profile never lets them
through).
Source code in src/agentic_ci/toolchains.py
write_results(path, results)
¶
Write results as a JSON list to path (_run/toolchains.json).