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
4 changes: 3 additions & 1 deletion .nanvix/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ def build(
stage test fixtures via ``_test.stage()``.
Comment thread
ada-x64 marked this conversation as resolved.
"""
_args = dataclasses.replace(args, targets=["build"])
dest_dir = paths.release_dir() if args.release else paths.test_out()
dest_dir = (
(paths.release_dir() / "sysroot-pkg") if args.release else paths.test_out()
)
if config.IS_WINDOWS:
# Build and install in one Docker invocation, writing directly to
# release_dir/test_out so ``./z test`` needs no further Docker work.
Comment thread
ada-x64 marked this conversation as resolved.
Expand Down
1 change: 1 addition & 0 deletions .nanvix/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ def configure_opts(
"python3-config",
f"python{PYTHON_VERSION}-config",
"python3",
f"python{PYTHON_VERSION}",
]

# ---------------------------------------------------------------------------
Expand Down
135 changes: 56 additions & 79 deletions .nanvix/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,75 +9,63 @@

from __future__ import annotations

from pathlib import Path
import shutil
import tarfile
import build as build_mod
import config
import lxml as lxml_mod
import ramfs as ramfs_mod
from nanvix_zutil import paths


def package(
args: build_mod.MakeArgs,
) -> None:
"""Package CPython release tarballs.

Creates two tarballs in ``nanvix_zutil.paths.dist_dir()``:
- ``cpython-<platform>-<mode>-<memory>.tar.gz`` — runtime sysroot + binary + ramfs
- ``cpython-<platform>-<mode>-<memory>-buildroot.tar.gz`` — build dependencies
"""
release_staging = paths.nanvix_root() / "release"
dist_dir = paths.dist_dir()
artifact = args.asset_prefix()
def sysroot_pkg() -> Path:
"""Staging tree for the runtime sysroot tarball (tarred verbatim)."""
return paths.release_dir() / "sysroot-pkg"

print("Packaging CPython release...")

# Clean previous staging.
if release_staging.exists():
shutil.rmtree(release_staging)
def buildroot_pkg() -> Path:
"""Staging tree for the buildroot tarball (tarred verbatim)."""
return paths.release_dir() / "buildroot-pkg"

# Build.
build_mod.build(args)

# Install into staging.
build_mod.install(release_staging, args)
def release_sysroot() -> Path:
"""The raw ``make install`` output, living inside :func:`sysroot_pkg`."""
return sysroot_pkg() / "sysroot"

sysroot_installed = release_staging / "sysroot"
if not sysroot_installed.is_dir():
raise FileNotFoundError(f"Install did not produce {sysroot_installed}")

# Stage lxml Python package into the installed sysroot.
lxml_mod.stage_lxml_runtime(sysroot_installed)
def stage() -> None:
"""Stage the two tarball trees under ``release_dir/``.

# --- Buildroot: build dependencies ---
buildroot_pkg = release_staging / "buildroot-pkg"
buildroot_pkg.mkdir(parents=True)
(buildroot_pkg / "lib").mkdir()
(buildroot_pkg / "bin").mkdir()
Must run after ``build_mod.build(args)`` has populated
:func:`release_sysroot`. Buildroot is curated *before* the sysroot
install tree is trimmed in-place.
"""
# --- 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 / "lib").mkdir(parents=True)
(br / "bin").mkdir(parents=True)

# Copy include directory.
inc_src = sysroot_installed / "include"
inc_src = release_sysroot() / "include"
if inc_src.is_dir():
shutil.copytree(inc_src, buildroot_pkg / "include")
shutil.copytree(inc_src, br / "include")

# Copy static libraries.
lib_src = sysroot_installed / "lib"
lib_src = release_sysroot() / "lib"
if lib_src.is_dir():
for lib_file in lib_src.glob("*.a"):
shutil.copy2(lib_file, buildroot_pkg / "lib" / lib_file.name)
shutil.copy2(lib_file, br / "lib" / lib_file.name)
# Copy pkgconfig.
pkgconfig = lib_src / "pkgconfig"
if pkgconfig.is_dir():
shutil.copytree(pkgconfig, buildroot_pkg / "lib" / "pkgconfig")
shutil.copytree(pkgconfig, br / "lib" / "pkgconfig")
# Copy config-3.12.
config_dir = lib_src / config.PYTHON_LIB_DIR / f"config-{config.PYTHON_VERSION}"
if config_dir.is_dir():
dest = (
buildroot_pkg
/ "lib"
/ config.PYTHON_LIB_DIR
/ f"config-{config.PYTHON_VERSION}"
br / "lib" / config.PYTHON_LIB_DIR / f"config-{config.PYTHON_VERSION}"
)
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(config_dir, dest)
Expand All @@ -93,61 +81,50 @@ def package(
"pydoc3",
f"pydoc{config.PYTHON_VERSION}",
]:
src = sysroot_installed / "bin" / f
src = release_sysroot() / "bin" / f
if src.is_file():
shutil.copy2(src, buildroot_pkg / "bin" / f)
shutil.copy2(src, br / "bin" / f)

# Copy share directory.
share_src = sysroot_installed / "share"
share_src = release_sysroot() / "share"
if share_src.is_dir():
shutil.copytree(share_src, buildroot_pkg / "share")
shutil.copytree(share_src, br / "share")

# --- Sysroot: runtime stdlib (trimmed) ---
ramfs_staging = release_staging / "sysroot-pkg-wrap"
ramfs_sysroot = ramfs_staging / "sysroot" / "lib"
ramfs_sysroot.mkdir(parents=True)

py_lib = sysroot_installed / "lib" / config.PYTHON_LIB_DIR
if py_lib.is_dir():
shutil.copytree(py_lib, ramfs_sysroot / config.PYTHON_LIB_DIR)

ramfs_mod.trim_sysroot(ramfs_staging)
# --- Sysroot tarball staging: trim install tree in-place ---
ramfs_mod.trim_sysroot(sysroot_pkg())

# --- Include python.elf binary ---
bin_dir = release_staging / "bin"
python_elf = paths.repo_root() / f"python{config.EXE}"
if python_elf.is_file():
bin_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(python_elf, bin_dir / "python.elf")
size = (bin_dir / "python.elf").stat().st_size
bin_dst = sysroot_pkg() / "bin"
bin_dst.mkdir(parents=True, exist_ok=True)
shutil.copy2(python_elf, bin_dst / "python.elf")
size = (bin_dst / "python.elf").stat().st_size
print(f"Included bin/python.elf ({size // 1024}K)")
else:
print("Warning: python.elf not found — binary will not be included in release")

# --- Build ramfs image ---
ramfs_img = release_staging / "cpython-ramfs.img"
ramfs_mod.build_image(ramfs_staging, args.sysroot, ramfs_img)

# --- Create release tarballs ---
dist_dir.mkdir(parents=True, exist_ok=True)
def package(
args: build_mod.MakeArgs,
) -> None:
"""Tar the two pre-staged trees verbatim.

# Sysroot tarball.
sysroot_tar = dist_dir / f"{artifact}.tar.gz"
sysroot_runtime = ramfs_staging / "sysroot"
with tarfile.open(str(sysroot_tar), "w:gz") as tf:
tf.add(str(sysroot_runtime), arcname="sysroot")
if bin_dir.is_dir():
tf.add(str(bin_dir), arcname="bin")
if ramfs_img.is_file():
tf.add(str(ramfs_img), arcname="cpython-ramfs.img")

# Buildroot tarball.
buildroot_tar = dist_dir / f"{artifact}-buildroot.tar.gz"
with tarfile.open(str(buildroot_tar), "w:gz") as tf:
tf.add(str(buildroot_pkg), arcname="sysroot")
Creates two tarballs in ``nanvix_zutil.paths.dist_dir()``:
- ``cpython-<platform>-<mode>-<memory>.tar.gz`` — runtime sysroot + binary + ramfs
- ``cpython-<platform>-<mode>-<memory>-buildroot.tar.gz`` — build dependencies
"""
dist_dir = paths.dist_dir()
artifact = args.asset_prefix()
dist_dir.mkdir(parents=True, exist_ok=True)

# Cleanup staging.
shutil.rmtree(release_staging)
for staging, name in [
(sysroot_pkg(), f"{artifact}.tar.gz"),
(buildroot_pkg(), f"{artifact}-buildroot.tar.gz"),
]:
with tarfile.open(str(dist_dir / name), "w:gz") as tf:
for child in sorted(staging.iterdir()):
tf.add(str(child), arcname=child.name)
Comment thread
ada-x64 marked this conversation as resolved.
Comment thread
ada-x64 marked this conversation as resolved.

print("Release tarballs created in dist/")
for f in sorted(dist_dir.glob(f"{artifact}*.tar.gz")):
Expand Down
19 changes: 9 additions & 10 deletions .nanvix/z.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@

import _test as test_mod
import build as build_mod
import lxml as lxml_mod
import config
import package as package_mod
import ramfs as ramfs_mod
from nanvix_zutil import (
CFG_SYSROOT,
EXIT_MISSING_DEP,
TOOLCHAIN_CONTAINER_PATH,
ZScript,
log,
make_initrd,
run,
suffix_dep,
)
Expand Down Expand Up @@ -211,11 +212,13 @@ def build(self) -> None:
build_mod.clean(preserve_nanvix_root=False, preserve_cache=True)
args = self._make_args(release=True)
build_mod.build(args)

# For standalone deployment mode, produce an initrd image
# containing the system daemons and the application binary.
if self.config.deployment_mode == "standalone":
make_initrd(self, f"python{config.EXE}", test=False)
lxml_mod.stage_lxml_runtime(package_mod.release_sysroot())
package_mod.stage()
ramfs_mod.build_image(
package_mod.sysroot_pkg(),
args.sysroot,
package_mod.sysroot_pkg() / "cpython-ramfs.img",
)

# Build for test
build_mod.clean(preserve_nanvix_root=True, preserve_cache=True)
Expand Down Expand Up @@ -257,10 +260,6 @@ def release(self) -> None:
def clean(self) -> None:
"""Remove build artifacts."""
build_mod.clean()
# Remove initrd image generated for standalone mode.
initrd = paths.repo_root() / "python.img"
if initrd.exists():
initrd.unlink()

def _install_missing_deps(self) -> None:
"""Download missing dependency libraries using fallback assets."""
Expand Down