deb-mock/deb_mock/config.py
2025-08-03 22:16:04 +00:00

279 lines
No EOL
12 KiB
Python

"""
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