""" Configuration management for deb-mock """ import os import yaml from pathlib import Path from typing import Dict, Any, Optional from .exceptions import ConfigurationError class Config: """Configuration class for deb-mock""" def __init__(self, **kwargs): # Default configuration self.chroot_name = kwargs.get('chroot_name', 'bookworm-amd64') self.architecture = kwargs.get('architecture', 'amd64') self.suite = kwargs.get('suite', 'bookworm') self.output_dir = kwargs.get('output_dir', './output') self.keep_chroot = kwargs.get('keep_chroot', False) self.verbose = kwargs.get('verbose', False) self.debug = kwargs.get('debug', False) # Chroot configuration self.basedir = kwargs.get('basedir', '/var/lib/deb-mock') self.chroot_dir = kwargs.get('chroot_dir', '/var/lib/deb-mock/chroots') self.chroot_config_dir = kwargs.get('chroot_config_dir', '/etc/schroot/chroot.d') self.chroot_home = kwargs.get('chroot_home', '/home/build') # sbuild configuration self.sbuild_config = kwargs.get('sbuild_config', '/etc/sbuild/sbuild.conf') self.sbuild_log_dir = kwargs.get('sbuild_log_dir', '/var/log/sbuild') # Build configuration self.build_deps = kwargs.get('build_deps', []) self.build_env = kwargs.get('build_env', {}) self.build_options = kwargs.get('build_options', []) # Metadata configuration self.metadata_dir = kwargs.get('metadata_dir', './metadata') self.capture_logs = kwargs.get('capture_logs', True) self.capture_changes = kwargs.get('capture_changes', True) # Speed optimization (Mock-inspired features) self.cache_dir = kwargs.get('cache_dir', '/var/cache/deb-mock') self.use_root_cache = kwargs.get('use_root_cache', True) self.root_cache_dir = kwargs.get('root_cache_dir', '/var/cache/deb-mock/root-cache') self.root_cache_age = kwargs.get('root_cache_age', 7) # days self.use_package_cache = kwargs.get('use_package_cache', True) self.package_cache_dir = kwargs.get('package_cache_dir', '/var/cache/deb-mock/package-cache') self.use_ccache = kwargs.get('use_ccache', False) self.ccache_dir = kwargs.get('ccache_dir', '/var/cache/deb-mock/ccache') self.use_tmpfs = kwargs.get('use_tmpfs', False) self.tmpfs_size = kwargs.get('tmpfs_size', '2G') # Parallel builds self.parallel_jobs = kwargs.get('parallel_jobs', 4) self.parallel_compression = kwargs.get('parallel_compression', True) # Network and proxy self.use_host_resolv = kwargs.get('use_host_resolv', True) self.http_proxy = kwargs.get('http_proxy', None) self.https_proxy = kwargs.get('https_proxy', None) self.no_proxy = kwargs.get('no_proxy', None) # Mirror configuration self.mirror = kwargs.get('mirror', 'http://deb.debian.org/debian/') self.security_mirror = kwargs.get('security_mirror', None) self.backports_mirror = kwargs.get('backports_mirror', None) # Isolation and security self.isolation = kwargs.get('isolation', 'schroot') # schroot, simple, nspawn self.enable_network = kwargs.get('enable_network', True) self.selinux_enabled = kwargs.get('selinux_enabled', False) # Bootstrap chroot support (Mock FAQ #2 - Cross-distribution builds) self.use_bootstrap_chroot = kwargs.get('use_bootstrap_chroot', False) self.bootstrap_chroot_name = kwargs.get('bootstrap_chroot_name', None) self.bootstrap_arch = kwargs.get('bootstrap_arch', None) self.bootstrap_suite = kwargs.get('bootstrap_suite', None) # Build environment customization self.chroot_setup_cmd = kwargs.get('chroot_setup_cmd', []) self.chroot_additional_packages = kwargs.get('chroot_additional_packages', []) # Environment variable preservation (Mock FAQ #1) self.preserve_environment = kwargs.get('preserve_environment', []) self.environment_sanitization = kwargs.get('environment_sanitization', True) self.allowed_environment_vars = kwargs.get('allowed_environment_vars', [ 'DEB_BUILD_OPTIONS', 'DEB_BUILD_PROFILES', 'CC', 'CXX', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS', 'MAKEFLAGS', 'CCACHE_DIR', 'CCACHE_HASHDIR', 'http_proxy', 'https_proxy', 'no_proxy', 'DISPLAY', 'XAUTHORITY' ]) # Advanced build options (Mock-inspired) self.run_tests = kwargs.get('run_tests', True) self.build_timeout = kwargs.get('build_timeout', 0) # 0 = no timeout self.force_architecture = kwargs.get('force_architecture', None) self.unique_extension = kwargs.get('unique_extension', None) self.config_dir = kwargs.get('config_dir', None) self.cleanup_after = kwargs.get('cleanup_after', True) # APT configuration self.apt_sources = kwargs.get('apt_sources', []) self.apt_preferences = kwargs.get('apt_preferences', []) self.apt_command = kwargs.get('apt_command', 'apt-get') self.apt_install_command = kwargs.get('apt_install_command', 'apt-get install -y') # Plugin configuration self.plugins = kwargs.get('plugins', {}) self.plugin_dir = kwargs.get('plugin_dir', '/usr/lib/deb-mock/plugins') @classmethod def from_file(cls, config_path: str) -> 'Config': """Load configuration from a YAML file""" try: with open(config_path, 'r') as f: config_data = yaml.safe_load(f) return cls(**config_data) except FileNotFoundError: raise ConfigurationError(f"Configuration file not found: {config_path}") except yaml.YAMLError as e: raise ConfigurationError(f"Invalid YAML in configuration file: {e}") except Exception as e: raise ConfigurationError(f"Error loading configuration: {e}") @classmethod def default(cls) -> 'Config': """Create default configuration""" return cls() def to_dict(self) -> Dict[str, Any]: """Convert configuration to dictionary""" return { 'chroot_name': self.chroot_name, 'architecture': self.architecture, 'suite': self.suite, 'output_dir': self.output_dir, 'keep_chroot': self.keep_chroot, 'verbose': self.verbose, 'debug': self.debug, 'chroot_dir': self.chroot_dir, 'chroot_config_dir': self.chroot_config_dir, 'sbuild_config': self.sbuild_config, 'sbuild_log_dir': self.sbuild_log_dir, 'build_deps': self.build_deps, 'build_env': self.build_env, 'build_options': self.build_options, 'metadata_dir': self.metadata_dir, 'capture_logs': self.capture_logs, 'capture_changes': self.capture_changes, 'use_root_cache': self.use_root_cache, 'root_cache_dir': self.root_cache_dir, 'root_cache_age': self.root_cache_age, 'use_package_cache': self.use_package_cache, 'package_cache_dir': self.package_cache_dir, 'use_ccache': self.use_ccache, 'ccache_dir': self.ccache_dir, 'use_tmpfs': self.use_tmpfs, 'tmpfs_size': self.tmpfs_size, 'parallel_jobs': self.parallel_jobs, 'parallel_compression': self.parallel_compression, 'use_host_resolv': self.use_host_resolv, 'http_proxy': self.http_proxy, 'https_proxy': self.https_proxy, 'no_proxy': self.no_proxy, 'mirror': self.mirror, 'security_mirror': self.security_mirror, 'backports_mirror': self.backports_mirror, 'isolation': self.isolation, 'enable_network': self.enable_network, 'selinux_enabled': self.selinux_enabled, 'use_bootstrap_chroot': self.use_bootstrap_chroot, 'bootstrap_chroot_name': self.bootstrap_chroot_name, 'bootstrap_arch': self.bootstrap_arch, 'bootstrap_suite': self.bootstrap_suite, 'chroot_setup_cmd': self.chroot_setup_cmd, 'chroot_additional_packages': self.chroot_additional_packages, 'preserve_environment': self.preserve_environment, 'environment_sanitization': self.environment_sanitization, 'allowed_environment_vars': self.allowed_environment_vars, } def save(self, config_path: str) -> None: """Save configuration to a YAML file""" try: config_dir = Path(config_path).parent config_dir.mkdir(parents=True, exist_ok=True) with open(config_path, 'w') as f: yaml.dump(self.to_dict(), f, default_flow_style=False) except Exception as e: raise ConfigurationError(f"Error saving configuration: {e}") def validate(self) -> None: """Validate configuration""" errors = [] # Check required directories if not os.path.exists(self.chroot_config_dir): errors.append(f"Chroot config directory does not exist: {self.chroot_config_dir}") if not os.path.exists(self.sbuild_config): errors.append(f"sbuild config file does not exist: {self.sbuild_config}") # Check architecture valid_architectures = ['amd64', 'i386', 'arm64', 'armhf', 'ppc64el', 's390x'] if self.architecture not in valid_architectures: errors.append(f"Invalid architecture: {self.architecture}") # Check suite valid_suites = ['bookworm', 'sid', 'bullseye', 'buster', 'jammy', 'noble', 'focal'] if self.suite not in valid_suites: errors.append(f"Invalid suite: {self.suite}") # Check isolation method valid_isolation = ['schroot', 'simple', 'nspawn'] if self.isolation not in valid_isolation: errors.append(f"Invalid isolation method: {self.isolation}") # Check parallel jobs if self.parallel_jobs < 1: errors.append("Parallel jobs must be at least 1") if errors: raise ConfigurationError(f"Configuration validation failed:\n" + "\n".join(errors)) def get_chroot_path(self) -> str: """Get the full path to the chroot directory""" return os.path.join(self.chroot_dir, self.chroot_name) def get_output_path(self) -> str: """Get the full path to the output directory""" return os.path.abspath(self.output_dir) def get_metadata_path(self) -> str: """Get the full path to the metadata directory""" return os.path.abspath(self.metadata_dir) def get_root_cache_path(self) -> str: """Get the full path to the root cache directory""" return os.path.join(self.root_cache_dir, self.chroot_name) def get_package_cache_path(self) -> str: """Get the full path to the package cache directory""" return os.path.join(self.package_cache_dir, self.chroot_name) def get_ccache_path(self) -> str: """Get the full path to the ccache directory""" return os.path.join(self.ccache_dir, self.chroot_name) def setup_build_environment(self) -> Dict[str, str]: """Setup build environment variables""" env = {} # Set parallel build options if self.parallel_jobs > 1: env['DEB_BUILD_OPTIONS'] = f"parallel={self.parallel_jobs},nocheck" env['MAKEFLAGS'] = f"-j{self.parallel_jobs}" # Set ccache if enabled if self.use_ccache: env['CCACHE_DIR'] = self.get_ccache_path() env['CCACHE_HASHDIR'] = '1' # Set proxy if configured if self.http_proxy: env['http_proxy'] = self.http_proxy if self.https_proxy: env['https_proxy'] = self.https_proxy if self.no_proxy: env['no_proxy'] = self.no_proxy # Merge with user-defined build environment env.update(self.build_env) return env