feat: add Perl to development environment and enhance toolchain accessibility

This commit is contained in:
maksyuki committed 2026-09-23 07:40:17 +08:00
1 parent b209ccedfe
commit 448d639d0e
6 files changed
+59 -2

No files matched your search

+1
View File
@@ -23,6 +23,7 @@ RUN apt-get update -qq \
mold \ mold \
numactl \ numactl \
openjdk-17-jre-headless \ openjdk-17-jre-headless \
perl \
python3 \ python3 \
python3-pip \ python3-pip \
python3-venv \ python3-venv \
+5 -1
View File
@@ -11,7 +11,8 @@ remote-bitbang debug acceptance flow. Docker, Nix, and manual Ubuntu installs
provide Java 17 for SBT. Docker and Nix use Python 3.10 to match the provide Java 17 for SBT. Docker and Nix use Python 3.10 to match the
hash-pinned Ubuntu 22.04 requirement sets. The environment also installs the hash-pinned Ubuntu 22.04 requirement sets. The environment also installs the
locked Python build and quality dependencies, clang-format-14, GNU Make, locked Python build and quality dependencies, clang-format-14, GNU Make,
C/C++ build tools, and runtime libraries required by those binaries. C/C++ build tools, Perl for Verilator, and runtime libraries required by those
binaries.
The environment intentionally does not include PDK repositories, managed RTL, The environment intentionally does not include PDK repositories, managed RTL,
application archives, build output, or compiler caches. These inputs are application archives, build output, or compiler caches. These inputs are
@@ -33,6 +34,9 @@ Python requirements, and writes:
Bootstrap is idempotent. It reinstalls tools or Python packages when the Bootstrap is idempotent. It reinstalls tools or Python packages when the
dependency lock, selected tool set, or Python requirement hashes change. dependency lock, selected tool set, or Python requirement hashes change.
Installed public tool archives are made readable and their directories
traversable by all users. This is required when Docker builds the image as root
and runs regressions with the caller's user ID.
~~~sh ~~~sh
python3 scripts/development_environment.py bootstrap python3 scripts/development_environment.py bootstrap
+1
View File
@@ -65,6 +65,7 @@
ncurses5 ncurses5
numactl numactl
jdk17_headless jdk17_headless
perl
python310Full python310Full
unzip unzip
which which
+1
View File
@@ -60,6 +60,7 @@ SYSTEM_EXECUTABLES = (
"make", "make",
"mold", "mold",
"numactl", "numactl",
"perl",
) )
STAMP_NAME = "development-environment.json" STAMP_NAME = "development-environment.json"
ACTIVATE_NAME = "activate.sh" ACTIVATE_NAME = "activate.sh"
+20
View File
@@ -6,9 +6,11 @@ import argparse
import os import os
import posixpath import posixpath
import shutil import shutil
import stat
import sys import sys
import tarfile import tarfile
import tempfile import tempfile
from itertools import chain
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
from typing import Any from typing import Any
@@ -38,6 +40,22 @@ def safe_extract(archive: Path, destination: Path) -> None:
bundle.extractall(destination) bundle.extractall(destination)
def make_tree_world_readable(root: Path) -> None:
"""Make a public tool archive usable by accounts other than its installer."""
for path in chain((root,), root.rglob("*")):
if path.is_symlink():
continue
mode = stat.S_IMODE(path.stat().st_mode)
if path.is_dir():
path.chmod(mode | 0o555)
elif path.is_file():
shared_mode = mode | 0o444
if mode & 0o111:
shared_mode |= 0o111
path.chmod(shared_mode)
def install(name: str, spec: dict[str, Any], cache: Path, update: bool) -> Path: def install(name: str, spec: dict[str, Any], cache: Path, update: bool) -> Path:
downloads = cache / "downloads" downloads = cache / "downloads"
archive = downloads / spec["archive"] archive = downloads / spec["archive"]
@@ -53,6 +71,7 @@ def install(name: str, spec: dict[str, Any], cache: Path, update: bool) -> Path:
destination = cache / "toolchains" / f"{name}-{spec['version']}" destination = cache / "toolchains" / f"{name}-{spec['version']}"
marker = destination / ".complete" marker = destination / ".complete"
if marker.is_file() and marker.read_text(encoding="utf-8").strip() == spec["sha256"]: if marker.is_file() and marker.read_text(encoding="utf-8").strip() == spec["sha256"]:
make_tree_world_readable(destination)
return destination / spec["path"] return destination / spec["path"]
destination.parent.mkdir(parents=True, exist_ok=True) destination.parent.mkdir(parents=True, exist_ok=True)
@@ -61,6 +80,7 @@ def install(name: str, spec: dict[str, Any], cache: Path, update: bool) -> Path:
extracted.mkdir() extracted.mkdir()
safe_extract(archive, extracted) safe_extract(archive, extracted)
atomic_write(extracted / ".complete", spec["sha256"] + "\n") atomic_write(extracted / ".complete", spec["sha256"] + "\n")
make_tree_world_readable(extracted)
if destination.exists(): if destination.exists():
shutil.rmtree(destination) shutil.rmtree(destination)
os.replace(extracted, destination) os.replace(extracted, destination)
+31 -1
View File
@@ -40,7 +40,7 @@ from scripts.development_environment import ( # noqa: E402
write_activation, write_activation,
) )
from scripts.generate_mpw import render_active_manifest, validate_extension_bindings # noqa: E402 from scripts.generate_mpw import render_active_manifest, validate_extension_bindings # noqa: E402
from scripts.install_toolchain import safe_extract # noqa: E402 from scripts.install_toolchain import make_tree_world_readable, safe_extract # noqa: E402
from scripts.package import make_sbom # noqa: E402 from scripts.package import make_sbom # noqa: E402
from scripts.prepare_mpw import patch_serv # noqa: E402 from scripts.prepare_mpw import patch_serv # noqa: E402
from scripts import regress # noqa: E402 from scripts import regress # noqa: E402
@@ -1193,10 +1193,12 @@ def test_container_and_nix_environment_files_use_locked_inputs() -> None:
assert f"ubuntu@{lock['container_images']['ubuntu_22_04']['digest']}" in dockerfile assert f"ubuntu@{lock['container_images']['ubuntu_22_04']['digest']}" in dockerfile
assert "openjdk-17-jre-headless" in dockerfile assert "openjdk-17-jre-headless" in dockerfile
assert " perl \\\n" in dockerfile
assert "scripts/development_environment.py" in dockerfile assert "scripts/development_environment.py" in dockerfile
assert "scripts/development_environment.py" in flake assert "scripts/development_environment.py" in flake
assert "buildFHSEnv" in flake assert "buildFHSEnv" in flake
assert "jdk17_headless" in flake assert "jdk17_headless" in flake
assert " perl\n" in flake
assert "python310Full" in flake assert "python310Full" in flake
assert "python3Full" not in flake assert "python3Full" not in flake
assert "retrosoc-development retrosoc-dev" in flake assert "retrosoc-development retrosoc-dev" in flake
@@ -2317,6 +2319,34 @@ def test_safe_extract_rejects_parent_traversal(tmp_path: Path) -> None:
assert not (tmp_path / "outside").exists() assert not (tmp_path / "outside").exists()
def test_toolchain_tree_is_accessible_to_runtime_users(tmp_path: Path) -> None:
toolchain = tmp_path / "toolchain"
binary_directory = toolchain / "bundle/bin"
binary_directory.mkdir(parents=True)
executable = binary_directory / "tool"
executable.write_text("#!/bin/sh\n", encoding="utf-8")
data = toolchain / "bundle/data"
data.write_text("public tool data\n", encoding="utf-8")
marker = toolchain / ".complete"
marker.write_text("digest\n", encoding="utf-8")
toolchain.chmod(0o700)
(toolchain / "bundle").chmod(0o700)
binary_directory.chmod(0o700)
executable.chmod(0o700)
data.chmod(0o600)
marker.chmod(0o600)
make_tree_world_readable(toolchain)
assert toolchain.stat().st_mode & 0o777 == 0o755
assert (toolchain / "bundle").stat().st_mode & 0o777 == 0o755
assert binary_directory.stat().st_mode & 0o777 == 0o755
assert executable.stat().st_mode & 0o777 == 0o755
assert data.stat().st_mode & 0o777 == 0o644
assert marker.stat().st_mode & 0o777 == 0o644
def test_fatfs_update_reextracts_downloaded_archive(tmp_path: Path) -> None: def test_fatfs_update_reextracts_downloaded_archive(tmp_path: Path) -> None:
module_path = ROOT / "app/setup.py" module_path = ROOT / "app/setup.py"
spec = importlib.util.spec_from_file_location("retrosoc_app_setup", module_path) spec = importlib.util.spec_from_file_location("retrosoc_app_setup", module_path)