Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions devenv/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@
!install-rust.sh
!install-uv.sh
!install-kani.sh
!install-cargo-tools.py
!cargo-tools.txt
!devenv-selftest.sh
!userns-setup
3 changes: 3 additions & 0 deletions devenv/Containerfile.c10s
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ COPY --from=kani /usr/local/rustup /usr/local/rustup
COPY --from=kani /usr/local/kani /usr/local/kani
# Point rustup at the system-wide installation, but let CARGO_HOME default to ~/.cargo
ENV RUSTUP_HOME=/usr/local/rustup
# Simple cargo-installable tools (cargo-vendor-filterer, cargo-edit, etc.)
COPY cargo-tools.txt install-cargo-tools.py /run/src/
RUN /run/src/install-cargo-tools.py
# Point Kani at the system-wide installation
ENV KANI_HOME=/usr/local/kani
# Configure uv for system-wide tool installation
Expand Down
3 changes: 3 additions & 0 deletions devenv/Containerfile.debian
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ COPY --from=kani /usr/local/rustup /usr/local/rustup
COPY --from=kani /usr/local/kani /usr/local/kani
# Point rustup at the system-wide installation, but let CARGO_HOME default to ~/.cargo
ENV RUSTUP_HOME=/usr/local/rustup
# Simple cargo-installable tools (cargo-vendor-filterer, cargo-edit, etc.)
COPY cargo-tools.txt install-cargo-tools.py /run/src/
RUN /run/src/install-cargo-tools.py
# Point Kani at the system-wide installation
ENV KANI_HOME=/usr/local/kani
# Setup for codespaces
Expand Down
3 changes: 3 additions & 0 deletions devenv/Containerfile.ubuntu
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ COPY --from=kani /usr/local/rustup /usr/local/rustup
COPY --from=kani /usr/local/kani /usr/local/kani
# Point rustup at the system-wide installation, but let CARGO_HOME default to ~/.cargo
ENV RUSTUP_HOME=/usr/local/rustup
# Simple cargo-installable tools (cargo-vendor-filterer, cargo-edit, etc.)
COPY cargo-tools.txt install-cargo-tools.py /run/src/
RUN /run/src/install-cargo-tools.py
# Point Kani at the system-wide installation
ENV KANI_HOME=/usr/local/kani
# Setup for codespaces
Expand Down
8 changes: 8 additions & 0 deletions devenv/cargo-tools.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Cargo tools installed via install-cargo-tools.sh
# Format: crate@version (one per line)
# Renovate annotations allow automated version updates.

# renovate: datasource=crate depName=cargo-vendor-filterer
cargo-vendor-filterer@0.5.18
# renovate: datasource=crate depName=cargo-edit
cargo-edit@0.13.9
113 changes: 113 additions & 0 deletions devenv/install-cargo-tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Install simple cargo tools listed in cargo-tools.txt.

Reads crate names and versions from cargo-tools.txt, installs each via
``cargo install --locked``, moves the resulting binaries to /usr/local/bin,
and cleans up cargo registry/build artifacts.

This script is shared between c10s, debian, and ubuntu container builds.
Prerequisites: rustup and a C linker (gcc) must already be installed.

For tools with special requirements (e.g. kani-verifier which needs
a setup step and its own KANI_HOME), use a dedicated install script instead.
"""

import os
import re
import subprocess
import sys
from pathlib import Path
Comment thread
cgwalters marked this conversation as resolved.

CARGO_HOME = Path("/usr/local/cargo")
INSTALL_DIR = Path("/usr/local/bin")

# Version strings must be alphanumeric with dots, hyphens, and an optional
# leading 'v'. This rejects path traversal sequences and other surprises.
_VERSION_RE = re.compile(r"^v?[A-Za-z0-9]+(?:[.\-][A-Za-z0-9]+)*$")


def parse_cargo_tools(path: Path) -> list[tuple[str, str]]:
"""Parse cargo-tools.txt, returning [(crate, version)] in order."""
tools = []
for lineno, line in enumerate(path.read_text().splitlines(), 1):
line = line.strip()
if not line or line.startswith("#"):
continue
if "@" not in line:
print(f"warning: skipping malformed line: {line}", file=sys.stderr)
continue
crate, version = line.split("@", 1)
if not _VERSION_RE.match(version):
print(
f"error: {path}:{lineno}: invalid version string: {version!r}",
file=sys.stderr,
)
sys.exit(1)
Comment thread
cgwalters marked this conversation as resolved.
tools.append((crate, version))
return tools


def install_crate(crate: str, version: str) -> None:
"""Install a single crate via cargo install."""
print(f"installing {crate}@{version}")
subprocess.run(
[
"/bin/time", "-f", "%E %C",
"cargo", "install", "--locked", crate, "--version", version,
],
check=True,
)
Comment thread
cgwalters marked this conversation as resolved.


def collect_binaries() -> None:
"""Move cargo-installed binaries to INSTALL_DIR.

Skips rustup-managed symlinks (cargo, rustc, rustup, etc.) which
are symlinks in CARGO_HOME/bin.
"""
cargo_bin = CARGO_HOME / "bin"
for entry in sorted(cargo_bin.iterdir()):
if entry.is_symlink():
continue
if not entry.is_file():
continue
dst = INSTALL_DIR / entry.name
entry.rename(dst)
Comment thread
cgwalters marked this conversation as resolved.
print(f"installed {dst}")


def cleanup() -> None:
"""Remove cargo registry and build artifacts."""
import shutil
Comment thread
cgwalters marked this conversation as resolved.

for subdir in ("registry", "git"):
p = CARGO_HOME / subdir
if p.exists():
shutil.rmtree(p)
print(f"cleaned {p}")


def main() -> None:
os.environ["RUSTUP_HOME"] = "/usr/local/rustup"
os.environ["CARGO_HOME"] = str(CARGO_HOME)
# Ensure cargo and rustc are on PATH
path = os.environ.get("PATH", "")
os.environ["PATH"] = f"/usr/local/bin:{path}"

script_dir = Path(__file__).parent
tools_file = script_dir / "cargo-tools.txt"
tools = parse_cargo_tools(tools_file)

if not tools:
print("error: no tools found in cargo-tools.txt", file=sys.stderr)
sys.exit(1)

for crate, version in tools:
install_crate(crate, version)

collect_binaries()
cleanup()


if __name__ == "__main__":
main()