From df49ac348750cc29b8afa60c20f0ae9739a1024c Mon Sep 17 00:00:00 2001 From: ada Date: Mon, 22 Jun 2026 11:30:17 -0500 Subject: [PATCH 1/5] Strip sysroot from build artifacts Strips sysroot/ directory from all build artifacts. Also removes dead code from config/ directory. Closes #742 --- .nanvix/_test.py | 66 ++++++++++++-------------- .nanvix/config.py | 113 +-------------------------------------------- .nanvix/lxml.py | 4 +- .nanvix/package.py | 15 ++---- .nanvix/ramfs.py | 28 +++++------ .nanvix/z.py | 2 +- Makefile.nanvix | 2 +- 7 files changed, 55 insertions(+), 175 deletions(-) diff --git a/.nanvix/_test.py b/.nanvix/_test.py index fe295aec4d3a53..80b108a4685c77 100644 --- a/.nanvix/_test.py +++ b/.nanvix/_test.py @@ -175,9 +175,7 @@ def _download_release_as_cache(args: build_mod.MakeArgs) -> Path: ) tf.extractall(cache_dir) - # The tarball contains {bin/, sysroot/, cpython-ramfs.img}. - # Restructure if needed so that sysroot/ is at cache_dir/sysroot/. - sysroot = cache_dir / "sysroot" + sysroot = cache_dir if not sysroot.is_dir(): python_lib_dir = Path(config.PYTHON_LIB_DIR) for candidate in cache_dir.rglob(str(python_lib_dir)): @@ -227,13 +225,13 @@ def stage(args: build_mod.MakeArgs) -> None: Also called by run_all on Windows CI. """ - sysroot_dir = paths.test_out() / "sysroot" + staging = paths.test_out() # Sysconfigdata fallback: ``make install`` should copy it from # build//, but can silently fail when PYTHON_FOR_BUILD # is unavailable or the install recipe is interrupted. scdata_name = f"{config.SYSCONFIGDATA_NAME}.py" - scdata_dst = sysroot_dir / "lib" / config.PYTHON_LIB_DIR / scdata_name + scdata_dst = staging / "lib" / config.PYTHON_LIB_DIR / scdata_name if not scdata_dst.is_file(): pybuilddir = paths.repo_root() / "pybuilddir.txt" if pybuilddir.is_file(): @@ -260,7 +258,7 @@ def stage(args: build_mod.MakeArgs) -> None: " print(f'CPYTHON_TEST_LXML_FAIL: {e}')\n" " sys.exit(1)\n" ) - (sysroot_dir / "test_hello.py").write_text( + (staging / "test_hello.py").write_text( "import sys\n" "print('CPYTHON_TEST_HELLO: Hello from Python', sys.version_info[:2])\n" "print('CPYTHON_TEST_PLATFORM:', sys.platform)\n" @@ -271,10 +269,10 @@ def stage(args: build_mod.MakeArgs) -> None: # ramfs build (standalone mode mounts ramfs as /). httpserver_src = paths.repo_root() / "httpserver.py" if httpserver_src.is_file(): - shutil.copy2(httpserver_src, sysroot_dir / "httpserver.py") + shutil.copy2(httpserver_src, staging / "httpserver.py") # Nanvix runtime binaries (host tools + guest daemons). - bin_dir = sysroot_dir / "bin" + bin_dir = staging / "bin" bin_dir.mkdir(parents=True, exist_ok=True) for binary in [ "nanvixd.elf", @@ -305,10 +303,10 @@ def stage(args: build_mod.MakeArgs) -> None: # Guest-side regrtest runner. regrtest_runner = paths.nanvix_root() / "run-regrtest.py" if regrtest_runner.is_file(): - shutil.copy2(regrtest_runner, sysroot_dir / "run-regrtest.py") + shutil.copy2(regrtest_runner, staging / "run-regrtest.py") # ``make install`` omits Lib/test/ from the install tree; regrtest needs it. - pylib_dir = sysroot_dir / "lib" / config.PYTHON_LIB_DIR + pylib_dir = staging / "lib" / config.PYTHON_LIB_DIR # Windows CI workaround: the synced artifact overlay only ships # *.elf/*.so (see nanvix_scripts.test_windows.mirror_ci), so the @@ -340,16 +338,15 @@ def stage_ramfs( Returns the path to the ramfs image. """ ramfs_img = paths.test_out() / "cpython-rootfs.img" - ramfs_cache = paths.test_out() / "_ramfs_cache" + ramfs_cache = paths.out_dir() / "_ramfs_cache" # Build fresh ramfs. if ramfs_cache.exists(): shutil.rmtree(ramfs_cache) - ramfs_cache.mkdir(parents=True) # Copy sysroot from test staging. - sysroot_src = paths.test_out() / "sysroot" - sysroot_dst = ramfs_cache / "sysroot" + sysroot_src = paths.test_out() + sysroot_dst = ramfs_cache shutil.copytree(sysroot_src, sysroot_dst) # Create /tmp for tempfile.gettempdir(). @@ -386,7 +383,6 @@ def _run_nanvixd_script( This is the low-level execution primitive shared by the hello-world test and the benchmark. """ - test_sysroot = staging / "sysroot" resolved_extra: list[str] = ( nanvixd_extra if nanvixd_extra is not None @@ -417,15 +413,15 @@ def _run_nanvixd_script( for name in _staging_bins: src = args.sysroot / "bin" / name if src.is_file(): - shutil.copy2(src, test_sysroot / "bin" / name) + shutil.copy2(src, staging / "bin" / name) initrd_img: Path | None = None if standalone: # Standalone: bundle python binary with system daemons into an # initrd image. Env vars are passed via app_env so the kernel's # split_cmdline sees them after the bare ';' separator. - bin_dir = test_sysroot / "bin" - app_path = test_sysroot / "bin" / config.python_binary() + bin_dir = staging / "bin" + app_path = staging / "bin" / config.python_binary() app_args = ["-B", f"./{script_name}"] app_env = ( f"PYTHONHOME=/ PYTHONDONTWRITEBYTECODE=1" @@ -463,7 +459,7 @@ def _run_nanvixd_script( capture_output=True, text=True, timeout=timeout, - cwd=test_sysroot, + cwd=staging, ) except subprocess.TimeoutExpired: raise RuntimeError(f"{label} timed out after {timeout}s") @@ -574,11 +570,10 @@ def run_smoke_httpserver( ) return - sysroot = staging / "sysroot" script_name = "httpserver.py" - if not (sysroot / script_name).is_file(): + if not (staging / script_name).is_file(): raise RuntimeError( - f"{script_name} not found in staging sysroot ({sysroot}); " + f"{script_name} not found in staging ({staging}); " "stage() did not copy it" ) @@ -587,7 +582,7 @@ def run_smoke_httpserver( if nanvixd_extra is not None else config.PLATFORM_NANVIXD_ARGS.get(args.platform, []) ) - nanvixd = str((sysroot / "bin" / config.nanvixd_binary()).resolve()) + nanvixd = str((staging / "bin" / config.nanvixd_binary()).resolve()) if ramfs_img is None: raise ValueError("ramfs_img is required for standalone mode") @@ -601,10 +596,10 @@ def run_smoke_httpserver( ): hp = args.sysroot / "bin" / name if hp.is_file(): - shutil.copy2(hp, sysroot / "bin" / name) + shutil.copy2(hp, staging / "bin" / name) - bin_dir = sysroot / "bin" - app_path = sysroot / "bin" / config.python_binary() + bin_dir = staging / "bin" + app_path = staging / "bin" / config.python_binary() app_args = ["-B", f"./{script_name}"] app_env = ( f"PYTHONHOME=/ PYTHONDONTWRITEBYTECODE=1" @@ -636,7 +631,7 @@ def run_smoke_httpserver( stdin=subprocess.DEVNULL, stdout=log_fh, stderr=subprocess.STDOUT, - cwd=sysroot, + cwd=staging, ) def _read_log() -> str: @@ -744,7 +739,6 @@ def run_regrtest( ) standalone = args.process_mode == "standalone" - sysroot = staging / "sysroot" run_tests_script = paths.nanvix_root() / "run-tests.py" env = os.environ.copy() @@ -755,7 +749,7 @@ def run_regrtest( # Standalone: ramfs + initrd-based invocation. if ramfs_img is None: ramfs_img = paths.nanvix_root() / "cpython-rootfs.img" - bin_dir = sysroot / "bin" + bin_dir = staging / "bin" extra_str = f"-bin-dir {bin_dir} -ramfs {ramfs_img}" if resolved_nanvixd_extra: extra_str += " " + " ".join(resolved_nanvixd_extra) @@ -777,7 +771,7 @@ def run_regrtest( cmd = [sys.executable, str(run_tests_script)] + test_list print(f"Test: regrtest ({len(test_list)} modules, {args.process_mode})...") - result = subprocess.run(cmd, cwd=sysroot, env=env) + result = subprocess.run(cmd, cwd=staging, env=env) if result.returncode != 0: raise RuntimeError(f"regrtest failed with exit code {result.returncode}") @@ -831,7 +825,7 @@ def run_all( if args.process_mode == "standalone": stage_ramfs(args) - lxml_mod.stage_lxml_runtime(staging / "sysroot") + lxml_mod.stage_lxml_runtime(staging) # Hello test. run_hello( @@ -902,14 +896,14 @@ def _run_benchmark_impl( """Inner implementation of :func:`run_benchmark`.""" # Consume the test install tree produced by ``./z build``. staging = paths.test_out() - if not (staging / "sysroot").is_dir(): + if not staging.is_dir(): raise RuntimeError( - f"{staging}/sysroot not found; run `./z build` before `./z benchmark`" + f"{staging} not found; run `./z build` before `./z benchmark`" ) # Write a minimal benchmark script. bench_script = "bench_hello.py" - (staging / "sysroot" / bench_script).write_text("print('hello world')\n") + (staging / bench_script).write_text("print('hello world')\n") # Build ramfs with release trimming (keep_tests=False). ramfs_img = None @@ -922,8 +916,8 @@ def _run_benchmark_impl( shutil.rmtree(bench_cache) bench_cache.mkdir(parents=True) - sysroot_src = staging / "sysroot" - sysroot_dst = bench_cache / "sysroot" + sysroot_src = staging + sysroot_dst = bench_cache shutil.copytree(sysroot_src, sysroot_dst) (sysroot_dst / "tmp").mkdir(exist_ok=True) diff --git a/.nanvix/config.py b/.nanvix/config.py index 90a4851d2da1d0..38734079113dbd 100644 --- a/.nanvix/config.py +++ b/.nanvix/config.py @@ -10,7 +10,6 @@ from __future__ import annotations import sys -from pathlib import Path # --------------------------------------------------------------------------- # Platform defaults @@ -20,7 +19,7 @@ DEFAULT_PLATFORM = "microvm" DEFAULT_PROCESS_MODE = "standalone" DEFAULT_MEMORY_SIZE = "256mb" -DEFAULT_INSTALL_PREFIX = "/sysroot" +DEFAULT_INSTALL_PREFIX = "/" # Python version string — centralized to avoid shotgun surgery if updated. PYTHON_VERSION = "3.12" @@ -49,116 +48,6 @@ DOCKER_WORKSPACE_PATH = "/mnt/workspace" -def toolchain_paths( - toolchain: str | Path, - sysroot: str | Path, -) -> dict[str, Path]: - """Return resolved paths to toolchain binaries and libraries.""" - tc = Path(toolchain) - sr = Path(sysroot) - return { - "cc": tc / "bin" / f"{TOOLCHAIN_TRIPLET}-gcc", - "cxx": tc / "bin" / f"{TOOLCHAIN_TRIPLET}-g++", - "ld": tc / "bin" / f"{TOOLCHAIN_TRIPLET}-ld", - "ar": tc / "bin" / f"{TOOLCHAIN_TRIPLET}-ar", - "ranlib": tc / "bin" / f"{TOOLCHAIN_TRIPLET}-ranlib", - "strip": tc / "bin" / f"{TOOLCHAIN_TRIPLET}-strip", - "build_python": tc / "bin" / "python3", - "libc": tc / f"{TOOLCHAIN_TRIPLET}" / "lib" / "libc.a", - "libm": tc / f"{TOOLCHAIN_TRIPLET}" / "lib" / "libm.a", - "libposix": sr / "lib" / "libposix.a", - "libcrt0": sr / "lib" / "libnvx_crt0.a", - "libz": sr / "lib" / "libz.a", - "libsqlite3": sr / "lib" / "libsqlite3.a", - "libssl": sr / "lib" / "libssl.a", - "libcrypto": sr / "lib" / "libcrypto.a", - "liblzma": sr / "lib" / "liblzma.a", - } - - -def configure_env(toolchain: str | Path, sysroot: str | Path) -> dict[str, str]: - """Return the environment dict for ./configure.""" - tp = toolchain_paths(toolchain, sysroot) - sr = Path(sysroot) - return { - "CC": str(tp["cc"]), - "CXX": str(tp["cxx"]), - "LD": str(tp["ld"]), - "AR": str(tp["ar"]), - "RANLIB": str(tp["ranlib"]), - "CFLAGS": ( - f"-O3 -fomit-frame-pointer -fno-unwind-tables " - f"-fno-asynchronous-unwind-tables -I{sr}/include" - ), - "CFLAGS_NODIST": "-fno-semantic-interposition", - "LDFLAGS": ( - f"-L{sr}/lib -T{sr}/lib/user.ld " - f"-Wl,--allow-multiple-definition -no-pie " - f"-Wl,--export-dynamic -Wl,--no-dynamic-linker" - ), - "LIBS": ( - f"-Wl,--start-group {tp['libcrt0']} {tp['libposix']} {tp['libc']} {tp['libm']} " - f"-lsqlite3 -lssl -lcrypto -lz -lbz2 -llzma -lffi -Wl,--end-group" - ), - "LIBSQLITE3_LIBS": f"-L{sr}/lib -lsqlite3", - "LIBSQLITE3_CFLAGS": f"-I{sr}/include", - "ZLIB_LIBS": f"-L{sr}/lib -lz", - "ZLIB_CFLAGS": f"-I{sr}/include", - "BZIP2_LIBS": f"-L{sr}/lib -lbz2", - "BZIP2_CFLAGS": f"-I{sr}/include", - "LIBLZMA_LIBS": f"-L{sr}/lib -llzma", - "LIBLZMA_CFLAGS": f"-I{sr}/include", - "LIBFFI_LIBS": f"-L{sr}/lib -lffi", - "LIBFFI_CFLAGS": f"-I{sr}/include", - } - - -def configure_opts( - build_python: str | Path, - libc: str | Path, - libm: str | Path, - sysroot: str | Path, - install_prefix: str = DEFAULT_INSTALL_PREFIX, - release: bool = False, -) -> list[str]: - """Return the ./configure option list.""" - opts = [ - "--disable-shared", - "--build=x86_64-pc-linux-gnux32", - f"--host={TOOLCHAIN_TRIPLET}", - f"--with-build-python={build_python}", - ] - if release: - opts.append("--disable-test-modules") - opts.extend( - [ - f"--with-libc={libc}", - f"--with-libm={libm}", - f"--prefix={install_prefix}", - f"--exec-prefix={install_prefix}", - "--with-ensurepip=no", - "--with-pkg-config=no", - f"--with-openssl={sysroot}", - "--disable-ipv6", - ] - ) - if release: - opts.append("--without-doc-strings") - opts.extend( - [ - "--with-computed-gotos", - "ac_cv_file__dev_ptmx=no", - "ac_cv_file__dev_ptc=no", - "ac_cv_pthread_is_default=yes", - "ac_cv_pthread=yes", - "ac_cv_kthread=no", - "ac_cv_func_dlopen=yes", - "ac_cv_header_dlfcn_h=yes", - ] - ) - return opts - - # --------------------------------------------------------------------------- # Test module lists # --------------------------------------------------------------------------- diff --git a/.nanvix/lxml.py b/.nanvix/lxml.py index 1174697a1148ca..bdd6bf0470ab8d 100644 --- a/.nanvix/lxml.py +++ b/.nanvix/lxml.py @@ -40,7 +40,7 @@ def clear_setup_local(repo_root: Path) -> None: print(f"[lxml] Removed {setup_local}") -def stage_lxml_runtime(sysroot: Path) -> None: +def stage_lxml_runtime(pkg_root: Path) -> None: """Copy lxml Python files from buildroot into the test/package sysroot. Looks for lxml in ``.nanvix/buildroot/python-packages/lxml/``. @@ -54,7 +54,7 @@ def stage_lxml_runtime(sysroot: Path) -> None: ) return - py_lib = sysroot / "lib" / config.PYTHON_LIB_DIR + py_lib = pkg_root / "lib" / config.PYTHON_LIB_DIR if not py_lib.is_dir(): raise RuntimeError(f"Python runtime library directory is missing: {py_lib}") diff --git a/.nanvix/package.py b/.nanvix/package.py index 5124bdeb2be77b..decbf7e01f10f7 100644 --- a/.nanvix/package.py +++ b/.nanvix/package.py @@ -28,16 +28,11 @@ def buildroot_pkg() -> Path: return paths.release_dir() / "buildroot-pkg" -def release_sysroot() -> Path: - """The raw ``make install`` output, living inside :func:`sysroot_pkg`.""" - return sysroot_pkg() / "sysroot" - - def stage() -> None: """Stage the two tarball trees under ``release_dir/``. Must run after ``build_mod.build(args)`` has populated - :func:`release_sysroot`. Buildroot is curated *before* the sysroot + :func:`sysroot_pkg`. Buildroot is curated *before* the sysroot install tree is trimmed in-place. """ # --- Buildroot tarball staging (must come before trim_sysroot) --- @@ -48,12 +43,12 @@ def stage() -> None: (br / "bin").mkdir(parents=True) # Copy include directory. - inc_src = release_sysroot() / "include" + inc_src = sysroot_pkg() / "include" if inc_src.is_dir(): shutil.copytree(inc_src, br / "include") # Copy static libraries. - lib_src = release_sysroot() / "lib" + lib_src = sysroot_pkg() / "lib" if lib_src.is_dir(): for lib_file in lib_src.glob("*.a"): shutil.copy2(lib_file, br / "lib" / lib_file.name) @@ -81,12 +76,12 @@ def stage() -> None: "pydoc3", f"pydoc{config.PYTHON_VERSION}", ]: - src = release_sysroot() / "bin" / f + src = sysroot_pkg() / "bin" / f if src.is_file(): shutil.copy2(src, br / "bin" / f) # Copy share directory. - share_src = release_sysroot() / "share" + share_src = sysroot_pkg() / "share" if share_src.is_dir(): shutil.copytree(share_src, br / "share") diff --git a/.nanvix/ramfs.py b/.nanvix/ramfs.py index 54fd1f0c8c1c02..84e1353b2be19a 100644 --- a/.nanvix/ramfs.py +++ b/.nanvix/ramfs.py @@ -13,6 +13,8 @@ import subprocess from pathlib import Path +from nanvix_zutil import paths + import config @@ -28,15 +30,14 @@ def trim_sysroot( keep_tests: When True, retain ``lib/python3.12/test/`` (needed when building a ramfs for the test pipeline). """ - sysroot = staging / "sysroot" - if not sysroot.is_dir(): - raise FileNotFoundError(f"{sysroot} does not exist") + if not staging.is_dir(): + raise FileNotFoundError(f"{staging} does not exist") print("Trimming sysroot for ramfs...") # Remove heavyweight stdlib packages not needed at runtime. for reldir in config.SYSROOT_TRIM_DIRS: - p = sysroot / reldir + p = staging / reldir if p.is_dir(): shutil.rmtree(p) elif p.is_file(): @@ -44,17 +45,17 @@ def trim_sysroot( # Optionally remove tests. if not keep_tests: - test_dir = sysroot / "lib" / config.PYTHON_LIB_DIR / "test" + test_dir = staging / "lib" / config.PYTHON_LIB_DIR / "test" if test_dir.is_dir(): shutil.rmtree(test_dir) # Remove static library. - lib_a = sysroot / "lib" / f"libpython{config.PYTHON_VERSION}.a" + lib_a = staging / "lib" / f"libpython{config.PYTHON_VERSION}.a" if lib_a.is_file(): lib_a.unlink() # Remove dev/config binaries from bin/. - bin_dir = sysroot / "bin" + bin_dir = staging / "bin" if bin_dir.is_dir(): for pattern in config.SYSROOT_TRIM_BIN_PATTERNS: for match in bin_dir.glob(pattern): @@ -73,7 +74,7 @@ def trim_sysroot( pass # Remove __pycache__ directories. - for cache_dir in sysroot.rglob("__pycache__"): + for cache_dir in staging.rglob("__pycache__"): if cache_dir.is_dir(): shutil.rmtree(cache_dir) @@ -109,14 +110,15 @@ def build_image( "Run `./z setup` to download required binaries." ) - sysroot = staging / "sysroot" - if not sysroot.is_dir(): - raise FileNotFoundError(f"{sysroot} does not exist") - + # Create a temporary image, then move it into place. Prevents cycles. + if not staging.is_dir(): + raise FileNotFoundError(f"{staging} does not exist") + prog = [str(mkramfs), "-o", str(paths.out_dir() / "tmp.img"), str(staging)] subprocess.run( - [str(mkramfs), "-o", str(output), str(sysroot)], + prog, check=True, ) + shutil.move(paths.out_dir() / "tmp.img", output) size = output.stat().st_size human = _human_size(size) diff --git a/.nanvix/z.py b/.nanvix/z.py index 93b7b22f845373..3a15ab21080ce5 100644 --- a/.nanvix/z.py +++ b/.nanvix/z.py @@ -212,7 +212,7 @@ def build(self) -> None: build_mod.clean(preserve_nanvix_root=False, preserve_cache=True) args = self._make_args(release=True) build_mod.build(args) - lxml_mod.stage_lxml_runtime(package_mod.release_sysroot()) + lxml_mod.stage_lxml_runtime(package_mod.sysroot_pkg()) package_mod.stage() ramfs_mod.build_image( package_mod.sysroot_pkg(), diff --git a/Makefile.nanvix b/Makefile.nanvix index dbf25dcfee5588..3d36766ba63830 100644 --- a/Makefile.nanvix +++ b/Makefile.nanvix @@ -27,7 +27,7 @@ PROCESS_MODE ?= standalone MEMORY_SIZE ?= 256mb # Install prefix baked into the python binary (sys.prefix, sys.path) -INSTALL_PREFIX ?= /sysroot +INSTALL_PREFIX ?= / # Set to 'yes' for release packaging NANVIX_RELEASE ?= no From d64c1c1dbf6fc110f855f195ef0f08acf7d213a9 Mon Sep 17 00:00:00 2001 From: ada Date: Mon, 22 Jun 2026 13:04:57 -0500 Subject: [PATCH 2/5] Fix(?) Windows CI --- .nanvix/_test.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.nanvix/_test.py b/.nanvix/_test.py index 80b108a4685c77..f5436d5f0d0913 100644 --- a/.nanvix/_test.py +++ b/.nanvix/_test.py @@ -175,18 +175,15 @@ def _download_release_as_cache(args: build_mod.MakeArgs) -> Path: ) tf.extractall(cache_dir) + # Flatten legacy tarballs that still wrap everything in a top-level + # ``sysroot/`` directory (releases predating the strip-sysroot change). + # Newer tarballs extract directly into ``cache_dir`` and this is a no-op. + extracted_wrapper = cache_dir / "sysroot" + if extracted_wrapper.is_dir(): + for item in extracted_wrapper.iterdir(): + shutil.move(str(item), str(cache_dir / item.name)) + extracted_wrapper.rmdir() sysroot = cache_dir - if not sysroot.is_dir(): - python_lib_dir = Path(config.PYTHON_LIB_DIR) - for candidate in cache_dir.rglob(str(python_lib_dir)): - parent = candidate - for _ in python_lib_dir.parts: - parent = parent.parent - if parent != cache_dir: - sysroot.mkdir(exist_ok=True) - for item in parent.iterdir(): - shutil.move(str(item), str(sysroot / item.name)) - break # Copy the stripped python binary into sysroot/bin/ if present. bin_dir = sysroot / "bin" From 71bdba4c615c284af8d09687d94dcb4c2e04e617 Mon Sep 17 00:00:00 2001 From: ada Date: Mon, 22 Jun 2026 13:27:36 -0500 Subject: [PATCH 3/5] address copilot feedback --- .nanvix/_test.py | 1 + .nanvix/package.py | 2 +- .nanvix/ramfs.py | 13 ++++++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.nanvix/_test.py b/.nanvix/_test.py index f5436d5f0d0913..eedd72f17d6b6a 100644 --- a/.nanvix/_test.py +++ b/.nanvix/_test.py @@ -338,6 +338,7 @@ def stage_ramfs( ramfs_cache = paths.out_dir() / "_ramfs_cache" # Build fresh ramfs. + paths.out_dir().mkdir(parents=True, exist_ok=True) if ramfs_cache.exists(): shutil.rmtree(ramfs_cache) diff --git a/.nanvix/package.py b/.nanvix/package.py index decbf7e01f10f7..2f9226d7616e5f 100644 --- a/.nanvix/package.py +++ b/.nanvix/package.py @@ -38,7 +38,7 @@ def stage() -> None: # --- Buildroot tarball staging (must come before trim_sysroot) --- if buildroot_pkg().is_dir(): shutil.rmtree(buildroot_pkg()) - br = buildroot_pkg() / "sysroot" # arcname "sysroot/" in the tarball + br = buildroot_pkg() (br / "lib").mkdir(parents=True) (br / "bin").mkdir(parents=True) diff --git a/.nanvix/ramfs.py b/.nanvix/ramfs.py index 84e1353b2be19a..154faf55f24dfd 100644 --- a/.nanvix/ramfs.py +++ b/.nanvix/ramfs.py @@ -9,8 +9,10 @@ from __future__ import annotations +import os import shutil import subprocess +import tempfile from pathlib import Path from nanvix_zutil import paths @@ -26,7 +28,9 @@ def trim_sysroot( """Strip dev-only artifacts from a staged sysroot for ramfs packaging. Args: - staging: Root directory containing a ``sysroot/`` subdirectory. + staging: Sysroot/install-tree root directory (i.e. the directory + containing ``bin/``, ``lib/``, ``include/``, …). Trimming is + performed in place. keep_tests: When True, retain ``lib/python3.12/test/`` (needed when building a ramfs for the test pipeline). """ @@ -87,8 +91,8 @@ def build_image( """Build a ramfs image from a trimmed sysroot. Args: - staging: Root directory containing a ``sysroot/`` subdirectory - (should be trimmed first via :func:`trim_sysroot`). + staging: Sysroot/install-tree root directory to package (should + be trimmed first via :func:`trim_sysroot`). nanvix_home: Path to the Nanvix sysroot (contains ``bin/mkramfs.elf`` or ``bin/mkramfs.exe``). output: Output path for the ramfs image. @@ -110,6 +114,9 @@ def build_image( "Run `./z setup` to download required binaries." ) + # Ensure paths.out_dir() + paths.out_dir().mkdir(parents=True, exist_ok=True) + # Create a temporary image, then move it into place. Prevents cycles. if not staging.is_dir(): raise FileNotFoundError(f"{staging} does not exist") From 9b98f26de22efe5de28027e0e07ba7681d4cc219 Mon Sep 17 00:00:00 2001 From: ada Date: Mon, 22 Jun 2026 13:35:53 -0500 Subject: [PATCH 4/5] Workflow fix --- .github/workflows/nanvix-ci.yml | 10 ++++++---- .nanvix/ramfs.py | 5 ----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/nanvix-ci.yml b/.github/workflows/nanvix-ci.yml index a6e5fee3ec0e1f..0a53e0f4520455 100644 --- a/.github/workflows/nanvix-ci.yml +++ b/.github/workflows/nanvix-ci.yml @@ -123,7 +123,11 @@ jobs: echo "Included: python.elf ($(stat -c%s windows-zip/python.elf) bytes)" # --- Build ramfs from the stdlib in the tarball --- - SYSROOT=$(find extract -type d -name "sysroot" | head -1) + # Tarballs ship a flat sysroot layout (no top-level `sysroot/` + # wrapper); the extraction root *is* the sysroot. The kernel mounts + # the ramfs at /, and Python is built with PYTHONHOME=/, so the + # image must be packed at the sysroot root (not its parent). + SYSROOT=extract PYLIB="${SYSROOT}/lib/python3.12" if [[ -d "$PYLIB" ]]; then echo "Building ramfs from stdlib at: $PYLIB" @@ -156,9 +160,7 @@ jobs: if [[ -n "$MKRAMFS" ]]; then chmod +x "$MKRAMFS" - # mkramfs expects the parent dir so FAT32 has sysroot/ prefix - STAGING_PARENT=$(dirname "$SYSROOT") - "$MKRAMFS" -o windows-zip/cpython-ramfs.img "$STAGING_PARENT" + "$MKRAMFS" -o windows-zip/cpython-ramfs.img "$SYSROOT" echo "Included: cpython-ramfs.img ($(stat -c%s windows-zip/cpython-ramfs.img) bytes)" else echo "::warning::mkramfs not found — zip will not contain ramfs" diff --git a/.nanvix/ramfs.py b/.nanvix/ramfs.py index 154faf55f24dfd..02945722d304a5 100644 --- a/.nanvix/ramfs.py +++ b/.nanvix/ramfs.py @@ -9,10 +9,8 @@ from __future__ import annotations -import os import shutil import subprocess -import tempfile from pathlib import Path from nanvix_zutil import paths @@ -114,9 +112,6 @@ def build_image( "Run `./z setup` to download required binaries." ) - # Ensure paths.out_dir() - paths.out_dir().mkdir(parents=True, exist_ok=True) - # Create a temporary image, then move it into place. Prevents cycles. if not staging.is_dir(): raise FileNotFoundError(f"{staging} does not exist") From 6dca468e760bcb932cfcfad857b49fd123ac9a40 Mon Sep 17 00:00:00 2001 From: ada Date: Tue, 23 Jun 2026 12:22:31 -0500 Subject: [PATCH 5/5] Fixup Windows docker builds --- .nanvix/_docker.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.nanvix/_docker.py b/.nanvix/_docker.py index e459912a965ad9..fa57f75a43877f 100644 --- a/.nanvix/_docker.py +++ b/.nanvix/_docker.py @@ -184,6 +184,12 @@ def docker_build( f"DESTDIR={config.DOCKER_WORKSPACE_PATH}/_install_staging", ] _args = dataclasses.replace(_args, targets=targets) + # The named Docker volume persists across builds; wipe the + # install staging dir so stale files (e.g. from a prior + # INSTALL_PREFIX) don't leak into the tarball. + clean_install_staging = ( + f"rm -rf {config.DOCKER_WORKSPACE_PATH}/_install_staging" + ) try: rel_dest = install_destdir.relative_to(workspace).as_posix() except ValueError: @@ -207,7 +213,8 @@ def docker_build( f"/mnt/host-workspace/{rel_dest}/" ) shell_cmd += ( - f" && {_args.to_string()} && {strip_install}; rc=$?; " + f" && {clean_install_staging} && {_args.to_string()}" + f" && {strip_install}; rc=$?; " f"{copy_back}; {install_copy}; exit $rc" ) else: @@ -250,8 +257,13 @@ def docker_install( f'[ -x "{strip_bin}" ] && [ -f "{install_bin}" ] && ' f'"{strip_bin}" --strip-all "{install_bin}" || true' ) + # The named Docker volume persists across builds; wipe the install + # staging dir so stale files (e.g. from a prior INSTALL_PREFIX) + # don't leak into the tarball. shell_cmd = ( - f"{sync} && cd {config.DOCKER_WORKSPACE_PATH} && {_args.to_string()}; rc=$?; " + f"{sync} && cd {config.DOCKER_WORKSPACE_PATH} && " + f"rm -rf {config.DOCKER_WORKSPACE_PATH}/_install_staging && " + f"{_args.to_string()}; rc=$?; " f"{strip_cmd}; " f"if [ -d {config.DOCKER_WORKSPACE_PATH}/_install_staging ]; then " f"mkdir -p /mnt/host-workspace/{rel_dest} && "