Skip to content

slot_builder

๐Ÿค– AI-Generated Content

This documentation was generated with AI assistance and is still being audited. Some, or potentially a lot, of this information may be inaccurate. Learn more.

flavor.packaging.python.slot_builder

Python slot builder for packaging operations.

Classes

PythonSlotBuilder

PythonSlotBuilder(
    manifest_dir: Path,
    package_name: str,
    entry_point: str,
    python_version: str = "3.11",
    is_windows: bool = False,
    manylinux_tag: str = "manylinux2014",
    build_config: dict[str, Any] | None = None,
    wheel_builder: Any = None,
)

Manages slot preparation and artifact assembly for Python packages.

Initialize slot builder.

Parameters:

Name Type Description Default
manifest_dir Path

Directory containing package manifest

required
package_name str

Name of the package

required
entry_point str

Entry point for the package

required
python_version str

Python version to use

'3.11'
is_windows bool

Whether building for Windows

False
manylinux_tag str

Manylinux tag for Linux compatibility

'manylinux2014'
build_config dict[str, Any] | None

Build configuration dictionary

None
wheel_builder Any

WheelBuilder instance for building wheels

None
Source code in flavor/packaging/python/slot_builder.py
def __init__(
    self,
    manifest_dir: Path,
    package_name: str,
    entry_point: str,
    python_version: str = "3.11",
    is_windows: bool = False,
    manylinux_tag: str = "manylinux2014",
    build_config: dict[str, Any] | None = None,
    wheel_builder: Any = None,
) -> None:
    """Initialize slot builder.

    Args:
        manifest_dir: Directory containing package manifest
        package_name: Name of the package
        entry_point: Entry point for the package
        python_version: Python version to use
        is_windows: Whether building for Windows
        manylinux_tag: Manylinux tag for Linux compatibility
        build_config: Build configuration dictionary
        wheel_builder: WheelBuilder instance for building wheels
    """
    self.manifest_dir = manifest_dir
    self.package_name = package_name
    self.entry_point = entry_point
    self.python_version = python_version
    self.is_windows = is_windows
    self.manylinux_tag = manylinux_tag
    self.build_config = build_config or {}
    self.wheel_builder = wheel_builder
    self.uv_manager = UVManager()
    self.env_builder = PythonEnvironmentBuilder(
        python_version=python_version,
        is_windows=is_windows,
        manylinux_tag=manylinux_tag,
    )
    self.uv_exe = "uv.exe" if is_windows else "uv"
Functions
prepare_artifacts
prepare_artifacts(work_dir: Path) -> dict[str, Path]

Prepare all artifacts needed for flavor assembly.

Returns:

Type Description
dict[str, Path]

Dictionary mapping artifact names to their paths:

dict[str, Path]
  • payload_tgz: The main payload archive
dict[str, Path]
  • metadata_tgz: Metadata archive
dict[str, Path]
  • uv_binary: UV binary (if available)
dict[str, Path]
  • python_tgz: Python distribution (placeholder for now)
Source code in flavor/packaging/python/slot_builder.py
def prepare_artifacts(self, work_dir: Path) -> dict[str, Path]:
    """
    Prepare all artifacts needed for flavor assembly.

    Returns:
        Dictionary mapping artifact names to their paths:
        - payload_tgz: The main payload archive
        - metadata_tgz: Metadata archive
        - uv_binary: UV binary (if available)
        - python_tgz: Python distribution (placeholder for now)
    """
    artifacts = {}

    # Create payload structure
    payload_dir = work_dir / "payload"
    ensure_dir(payload_dir, mode=DEFAULT_DIR_PERMS)
    artifacts["payload_dir"] = payload_dir

    # Build wheels
    wheels_dir = payload_dir / "wheels"
    ensure_dir(wheels_dir, mode=DEFAULT_DIR_PERMS)
    self._build_wheels(wheels_dir)

    # Ensure bin directory exists for UV binary
    bin_dir = payload_dir / "bin"
    ensure_dir(bin_dir, mode=DEFAULT_DIR_PERMS)
    logger.debug(f"Created bin directory: {bin_dir}")

    # Handle UV binary - download manylinux2014 version on Linux, copy from host on other platforms
    uv_obtained = False
    current_os = get_os_name()
    current_arch = get_arch_name()
    logger.info(f"Handling UV binary for {current_os}_{current_arch}")

    if current_os == "linux":
        # Download manylinux2014-compatible UV wheel for Linux using UVManager
        logger.info("Linux detected: downloading manylinux2014-compatible UV")
        try:
            payload_uv = self.uv_manager.download_uv_binary(bin_dir)
            if not payload_uv:
                # If download returns None on Linux, this is a critical error
                # since we need manylinux compatibility
                raise FileNotFoundError(
                    f"Failed to download {self.manylinux_tag}-compatible UV wheel for Linux. "
                    "This is required for broad Linux compatibility (glibc 2.17+)."
                )

            # Also copy to work dir for compatibility
            work_uv = work_dir / "uv"
            self._copy_executable(payload_uv, work_uv)
            artifacts["uv_binary"] = work_uv
            uv_obtained = True
        except Exception as e:
            # Re-raise with more context
            error_msg = f"Critical error downloading UV for Linux: {e}"
            logger.error(error_msg)
            raise FileNotFoundError(error_msg) from e

    # Fall back to copying from host if download failed or not on Linux
    if not uv_obtained:
        logger.debug("Attempting to find UV on host system")
        uv_host_path = self.env_builder.find_uv_command(raise_if_not_found=False)

        if uv_host_path:
            logger.info(f"Found UV on host at {uv_host_path}")
            # Copy to payload bin directory - always bin/ regardless of platform
            # UV goes in {workenv}/bin/uv (or uv.exe on Windows)
            payload_uv = bin_dir / self.uv_exe
            self._copy_executable(uv_host_path, payload_uv)

            # Also copy to work dir for Go/Rust packager compatibility
            work_uv = work_dir / self.uv_exe
            self._copy_executable(uv_host_path, work_uv)
            artifacts["uv_binary"] = work_uv
            logger.debug(f"UV binary copied to work dir: {work_uv}")
        else:
            # We still need to provide UV somehow - this is a critical error for Python packages
            raise FileNotFoundError(
                "UV binary not found on host system. Cannot build Python package without UV."
            )

    # Create metadata
    metadata_dir = payload_dir / "metadata"
    ensure_dir(metadata_dir, mode=DEFAULT_DIR_PERMS)
    self._create_metadata(metadata_dir)

    # Create payload archive with gzip -9 compression
    logger.info("Creating payload archive with maximum compression...")
    payload_tgz = work_dir / "payload.tgz"
    with tarfile.open(payload_tgz, "w:gz", compresslevel=9) as tar:
        # Sort files for deterministic build
        for f in sorted(payload_dir.rglob("*")):
            tar.add(f, arcname=f.relative_to(payload_dir))
    artifacts["payload_tgz"] = payload_tgz

    # Log the compressed size
    payload_tgz.stat().st_size / (1024 * 1024)

    # Create metadata archive (separate for selective extraction)
    metadata_content = work_dir / "metadata_content"
    ensure_dir(metadata_content, mode=DEFAULT_DIR_PERMS)
    # For now empty, but could contain launcher-specific metadata
    metadata_tgz = work_dir / "metadata.tgz"
    with tarfile.open(metadata_tgz, "w:gz", compresslevel=9) as tar:
        tar.add(metadata_content, arcname=".")
    artifacts["metadata_tgz"] = metadata_tgz

    # Create Python distribution placeholder
    python_tgz = work_dir / "python.tgz"
    self.env_builder.create_python_placeholder(python_tgz)
    artifacts["python_tgz"] = python_tgz

    return artifacts
resolve_transitive_dependencies
resolve_transitive_dependencies(
    dep_path: Path,
    seen: set[Path] | None = None,
    depth: int = 0,
) -> list[Path]

Recursively resolve all transitive local dependencies.

Parameters:

Name Type Description Default
dep_path Path

Path to a local dependency

required
seen set[Path] | None

Set of already-seen paths to avoid cycles

None
depth int

Current recursion depth for logging

0

Returns:

Type Description
list[Path]

List of all transitive dependency paths in dependency order (deepest first)

Source code in flavor/packaging/python/slot_builder.py
def resolve_transitive_dependencies(
    self, dep_path: Path, seen: set[Path] | None = None, depth: int = 0
) -> list[Path]:
    """
    Recursively resolve all transitive local dependencies.

    Args:
        dep_path: Path to a local dependency
        seen: Set of already-seen paths to avoid cycles
        depth: Current recursion depth for logging

    Returns:
        List of all transitive dependency paths in dependency order (deepest first)
    """
    if seen is None:
        seen = set()
        logger.info("๐Ÿ”๐Ÿ”„๐Ÿš€ Starting transitive dependency resolution")

    dep_path = dep_path.resolve()
    logger.debug("๐Ÿ” Checking dependency", name=dep_path.name, path=str(dep_path), depth=depth)

    if dep_path in seen:
        logger.debug("โญ๏ธ Dependency already processed", name=dep_path.name, depth=depth)
        return []

    seen.add(dep_path)
    all_deps: list[Path] = []

    # Get sub-dependencies from pyproject.toml
    sub_deps = self._get_flavor_subdeps(dep_path, depth)
    if sub_deps:
        logger.info("๐Ÿ”— Found sub-dependencies", count=len(sub_deps), parent=dep_path.name, depth=depth)

    # Recursively process each sub-dependency
    for sub_dep in sub_deps:
        sub_dep_path = dep_path / sub_dep
        if sub_dep_path.exists():
            logger.debug("๐Ÿ”„๐Ÿ”๐Ÿ“‹ Recursing into sub-dependency", name=sub_dep_path.name, depth=depth + 1)
            transitive = self.resolve_transitive_dependencies(sub_dep_path, seen, depth + 1)
            all_deps.extend(transitive)
        else:
            logger.warning("โš ๏ธ Sub-dependency not found", path=str(sub_dep_path), depth=depth)

    # Add this dependency after its dependencies (post-order)
    if dep_path not in all_deps:
        all_deps.append(dep_path)
        logger.info("โœ… Added dependency to build order", name=dep_path.name, depth=depth)

    if depth == 0:
        for i, dep in enumerate(all_deps, 1):
            logger.info("๐Ÿ“‹ Dependency build order", index=i, name=dep.name, path=str(dep))

    return all_deps