# builder/core/bootstrap.py import os import logging import platform import sys # Added import BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, BASE_DIR) try: # Assuming paths and logger_config are available through your project's structure # If paths is a dict defined elsewhere, you might need to pass it directly # from builder.core.bootstrap.paths import paths # Removed direct import, paths should be passed from builder.configs import logger_config # Assuming run_command is in builder.core.command_runner.py from builder.core.command_runner import run_command except ImportError as e: # Log the error before exiting logging.error(f"Error importing necessary modules: {e}. Ensure your environment is set up correctly.") # Provide more specific details if possible, though 'e' usually has them # logging.error(f"Details: {e}") # 'e' is already in the f-string above sys.exit(1) # Exit if essential modules can't be imported # Setup logger specifically for this module logger = logger_config.setup_logger('bootstrap') def filter_repositories_by_architecture(repositories_data, target_architecture): """ Filters the list of repositories to include only those supporting the target architecture. Args: repositories_data (list): A list of repository dictionaries, each potentially having an 'architectures' key. target_architecture (str): The target architecture (e.g., 'aarch64', 'i686', 'x86_64'). Returns: list: A list of '-R ' strings for the relevant repositories. """ filtered_repo_args = [] for repo in repositories_data: # If 'architectures' key exists and target_architecture is in the list, # or if 'architectures' key does not exist (assume it applies to all architectures). # Also ensure 'uri' key exists. if 'uri' in repo and ('architectures' not in repo or target_architecture in repo['architectures']): filtered_repo_args.extend(["-R", repo['uri']]) logger.debug(f"Including repository {repo['uri']} for architecture {target_architecture}") else: if 'uri' not in repo: logger.warning(f"Repository entry missing 'uri' key: {repo}. Skipping.") else: logger.debug(f"Excluding repository {repo['uri']} for architecture {target_architecture}") return filtered_repo_args def run_bootstrap_for_environment(env_name, paths, target_architecture, host_architecture, repositories_data, all_bootstrap_packages): """ Executes the xbps-install bootstrap command for a specific environment (rootfs, pep-host, pep-target). Handles architecture-specific repository filtering and execution in a chroot for cross-architecture builds. Args: env_name (str): The name of the environment to bootstrap ('rootfs', 'pep-host', 'pep-target'). paths (dict): Dictionary of build paths. target_architecture (str): The target architecture for the bootstrap. host_architecture (str): The architecture of the host system. repositories_data (list): List of repository dictionaries from YAML config. Expected structure: [{'name': '...', 'uri': '...', 'architectures': [...]}, ...] all_bootstrap_packages (dict): Dictionary containing package lists for different environments and architectures. Expected structure: {'env_name': {'arch': ['pkg1', ...]}} Raises: subprocess.CalledProcessError: If the xbps-install command fails. ValueError: If an unknown environment name is provided. """ logger.info(f"=> Executing bootstrap command for {env_name.upper()} ({target_architecture})...") # Determine the target directory based on environment name target_directory_path = None if env_name == "rootfs": target_directory_path = paths.get("ROOTFS") elif env_name == "pep-host": target_directory_path = paths.get("PEPHOSTDIR") elif env_name == "pep-target": target_directory_path = paths.get("PEPTARGETDIR") else: logger.error(f"Unknown bootstrap environment: {env_name}") raise ValueError(f"Unknown bootstrap environment: {env_name}") if not target_directory_path: logger.error(f"Path for environment '{env_name}' not found in paths dictionary.") raise KeyError(f"Path for environment '{env_name}' not found.") # Get the list of packages for this environment and target architecture # Assumes all_bootstrap_packages has structure {'env_name': {'arch': [...]}} packages_to_install = all_bootstrap_packages.get(env_name, {}).get(target_architecture, []) if not packages_to_install: logger.warning(f"No packages defined for {env_name} on architecture {target_architecture}. Skipping bootstrap.") return # Filter repository URLs for the target architecture filtered_repo_args = filter_repositories_by_architecture(repositories_data, target_architecture) if not filtered_repo_args: logger.error(f"No repositories found supporting target architecture: {target_architecture}. Cannot bootstrap {env_name}.") raise ValueError(f"No repositories found for architecture {target_architecture}") # Get the XBPS cache directory for the target architecture (on the host) # Assumes paths['XBPS_CACHEDIR_'] is defined or we construct it # Construct based on target arch as it's the target's packages being cached host_cachedir = os.path.join(os.getcwd(), "xbps_package_cache", target_architecture) # Ensure the cache directory exists os.makedirs(host_cachedir, exist_ok=True) logger.debug(f"Using XBPS cache directory (on host): {host_cachedir}") # Construct the base xbps-install command arguments xbps_command_args = [ "-S", "-y", # Sync and auto-yes ] + filtered_repo_args + [ "-r", target_directory_path, # Install to the target rootfs/directory (path on host) "-c", host_cachedir, ] + packages_to_install # Determine the command execution method (direct or via chroot) command_list = [] # Execute inside a chroot for ALL architectures for consistency and to handle # INSTALL scripts correctly, leveraging QEMU on foreign arches. logger.info(f"-> Running xbps-install for {env_name} in chroot...") # Note: The chroot command path is just 'chroot', relying on it being in the host's PATH # '/usr/bin/chroot' could be used for absolute path. command_list = [ "sudo", "chroot", target_directory_path, # Execute command inside this directory (path on host) "/usr/bin/xbps-install", # The xbps-install binary *inside* the chroot (path inside chroot) # Pass the rest of the xbps_command_args as arguments to xbps-install inside the chroot # Arguments like -r and -c need paths that are correct from the *host's* perspective, # which run_command handles by passing them literally. # The chroot environment needs /proc, /sys, /dev mounted for xbps and scripts. # The main script's mount_essential_filesystems_in_chroot should handle this *before* # calling this bootstrap function for rootfs. For pep-host/pep-target, manual mounts # inside this function or ensuring they are handled before might be needed if # they rely on those filesystems. Let's assume essential mounts are handled elsewhere # or not strictly needed for these specific xbps-install steps. ] + xbps_command_args # Execute the command # The run_command function should handle subprocess execution and error checking logger.info(f"Executing command: {' '.join(command_list)}") # Assuming run_command handles potential CalledProcessError and raises it run_command(command_list) logger.info(f"=> Void Linux {env_name.upper()} bootstrap COMPLETE.")