did stuff
This commit is contained in:
parent
472b3f0b9b
commit
39adb8dd06
4 changed files with 1419 additions and 0 deletions
537
debian_package_build_system.py
Normal file
537
debian_package_build_system.py
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Debian Package Build System Integration
|
||||
|
||||
This module integrates with Debian package building tools like sbuild and pbuilder,
|
||||
providing package building, validation, and testing capabilities.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
from dataclasses import dataclass, asdict
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class BuildEnvironment:
|
||||
"""Represents a Debian build environment"""
|
||||
name: str
|
||||
suite: str
|
||||
architecture: str
|
||||
mirror: str
|
||||
components: List[str]
|
||||
extra_repositories: List[str]
|
||||
build_dependencies: List[str]
|
||||
enabled: bool = True
|
||||
|
||||
@dataclass
|
||||
class BuildResult:
|
||||
"""Represents the result of a package build"""
|
||||
package_name: str
|
||||
version: str
|
||||
architecture: str
|
||||
suite: str
|
||||
build_status: str
|
||||
build_log: str
|
||||
artifacts: List[str]
|
||||
build_time: float
|
||||
dependencies_resolved: bool
|
||||
tests_passed: bool
|
||||
timestamp: datetime
|
||||
|
||||
class DebianPackageBuildSystem:
|
||||
"""Integrates with Debian package building tools"""
|
||||
|
||||
def __init__(self, config_dir: str = "./config/build-system"):
|
||||
self.config_dir = Path(config_dir)
|
||||
self.config_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.build_logs_dir = self.config_dir / "build-logs"
|
||||
self.build_logs_dir.mkdir(exist_ok=True)
|
||||
self.artifacts_dir = self.config_dir / "artifacts"
|
||||
self.artifacts_dir.mkdir(exist_ok=True)
|
||||
|
||||
self.logger = self._setup_logging()
|
||||
self._load_configuration()
|
||||
|
||||
def _setup_logging(self) -> logging.Logger:
|
||||
"""Setup logging for build system"""
|
||||
logger = logging.getLogger('debian-build-system')
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
if not logger.handlers:
|
||||
handler = logging.FileHandler(self.config_dir / "build-system.log")
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
return logger
|
||||
|
||||
def _load_configuration(self):
|
||||
"""Load build system configuration"""
|
||||
config_file = self.config_dir / "build-environments.json"
|
||||
|
||||
if config_file.exists():
|
||||
with open(config_file, 'r') as f:
|
||||
self.build_environments = json.load(f)
|
||||
else:
|
||||
self.build_environments = self._get_default_environments()
|
||||
self._save_configuration()
|
||||
|
||||
def _get_default_environments(self) -> Dict[str, Any]:
|
||||
"""Get default build environment configurations"""
|
||||
return {
|
||||
"environments": [
|
||||
{
|
||||
"name": "bookworm-amd64",
|
||||
"suite": "bookworm",
|
||||
"architecture": "amd64",
|
||||
"mirror": "http://deb.debian.org/debian",
|
||||
"components": ["main", "contrib", "non-free-firmware"],
|
||||
"extra_repositories": [],
|
||||
"build_dependencies": ["build-essential", "devscripts", "debhelper"],
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"name": "sid-amd64",
|
||||
"suite": "sid",
|
||||
"architecture": "amd64",
|
||||
"mirror": "http://deb.debian.org/debian",
|
||||
"components": ["main", "contrib", "non-free-firmware"],
|
||||
"extra_repositories": [],
|
||||
"build_dependencies": ["build-essential", "devscripts", "debhelper"],
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def _save_configuration(self):
|
||||
"""Save build system configuration"""
|
||||
config_file = self.config_dir / "build-environments.json"
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(self.build_environments, f, indent=2)
|
||||
|
||||
def check_build_tools(self) -> Dict[str, bool]:
|
||||
"""Check availability of Debian build tools"""
|
||||
tools = {
|
||||
'sbuild': self._check_command('sbuild'),
|
||||
'pbuilder': self._check_command('pbuilder'),
|
||||
'dpkg-buildpackage': self._check_command('dpkg-buildpackage'),
|
||||
'debuild': self._check_command('debuild'),
|
||||
'apt-get': self._check_command('apt-get'),
|
||||
'schroot': self._check_command('schroot')
|
||||
}
|
||||
|
||||
return tools
|
||||
|
||||
def _check_command(self, command: str) -> bool:
|
||||
"""Check if a command is available"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['which', command],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
return result.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def setup_build_environment(self, environment_name: str) -> bool:
|
||||
"""Setup a build environment using sbuild or pbuilder"""
|
||||
try:
|
||||
env_config = self._get_environment_config(environment_name)
|
||||
if not env_config:
|
||||
self.logger.error(f"Environment {environment_name} not found")
|
||||
return False
|
||||
|
||||
# Check if environment already exists
|
||||
if self._environment_exists(environment_name):
|
||||
self.logger.info(f"Environment {environment_name} already exists")
|
||||
return True
|
||||
|
||||
# Create environment
|
||||
if self._create_sbuild_environment(env_config):
|
||||
self.logger.info(f"Successfully created sbuild environment {environment_name}")
|
||||
return True
|
||||
elif self._create_pbuilder_environment(env_config):
|
||||
self.logger.info(f"Successfully created pbuilder environment {environment_name}")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Failed to create build environment {environment_name}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Environment setup failed: {e}")
|
||||
return False
|
||||
|
||||
def _get_environment_config(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get environment configuration by name"""
|
||||
for env in self.build_environments["environments"]:
|
||||
if env["name"] == name:
|
||||
return env
|
||||
return None
|
||||
|
||||
def _environment_exists(self, name: str) -> bool:
|
||||
"""Check if build environment exists"""
|
||||
# Check sbuild
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['schroot', '-l'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
return name in result.stdout
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check pbuilder
|
||||
pbuilder_dir = Path(f"/var/cache/pbuilder/{name}")
|
||||
return pbuilder_dir.exists()
|
||||
|
||||
def _create_sbuild_environment(self, config: Dict[str, Any]) -> bool:
|
||||
"""Create sbuild environment"""
|
||||
try:
|
||||
# Create schroot configuration
|
||||
schroot_conf = f"""
|
||||
[{config['name']}]
|
||||
description=Debian {config['suite']} {config['architecture']} build environment
|
||||
directory=/var/chroot/{config['name']}
|
||||
root-users=root
|
||||
users=buildd
|
||||
type=directory
|
||||
profile=sbuild
|
||||
"""
|
||||
|
||||
schroot_conf_file = Path(f"/etc/schroot/chroot.d/{config['name']}")
|
||||
schroot_conf_file.write_text(schroot_conf)
|
||||
|
||||
# Create chroot directory
|
||||
chroot_dir = Path(f"/var/chroot/{config['name']}")
|
||||
chroot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Bootstrap the chroot
|
||||
cmd = [
|
||||
'debootstrap',
|
||||
'--arch', config['architecture'],
|
||||
'--variant=buildd',
|
||||
config['suite'],
|
||||
str(chroot_dir),
|
||||
config['mirror']
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
self.logger.error(f"Debootstrap failed: {result.stderr}")
|
||||
return False
|
||||
|
||||
# Install build dependencies
|
||||
self._install_build_dependencies(config, chroot_dir)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Sbuild environment creation failed: {e}")
|
||||
return False
|
||||
|
||||
def _create_pbuilder_environment(self, config: Dict[str, Any]) -> bool:
|
||||
"""Create pbuilder environment"""
|
||||
try:
|
||||
# Create pbuilder configuration
|
||||
pbuilder_conf = f"""
|
||||
DISTRIBUTION={config['suite']}
|
||||
ARCHITECTURE={config['architecture']}
|
||||
MIRRORSITE={config['mirror']}
|
||||
COMPONENTS="{' '.join(config['components'])}"
|
||||
OTHERMIRROR="{' '.join(config['extra_repositories'])}"
|
||||
BUILDRESULT=/var/cache/pbuilder/result
|
||||
APTCACHEHARDLINK=yes
|
||||
USEPROC=yes
|
||||
USEDEVPTS=yes
|
||||
USEDEVFS=no
|
||||
BUILDPLACE=/var/cache/pbuilder/build
|
||||
"""
|
||||
|
||||
pbuilder_conf_file = Path(f"/etc/pbuilderrc-{config['name']}")
|
||||
pbuilder_conf_file.write_text(pbuilder_conf)
|
||||
|
||||
# Create pbuilder environment
|
||||
cmd = [
|
||||
'pbuilder', '--create',
|
||||
'--configfile', str(pbuilder_conf_file),
|
||||
'--basetgz', f"/var/cache/pbuilder/{config['name']}-base.tgz"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
self.logger.error(f"Pbuilder creation failed: {result.stderr}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Pbuilder environment creation failed: {e}")
|
||||
return False
|
||||
|
||||
def _install_build_dependencies(self, config: Dict[str, Any], chroot_dir: Path):
|
||||
"""Install build dependencies in chroot"""
|
||||
try:
|
||||
# Mount proc and dev
|
||||
subprocess.run(['mount', '--bind', '/proc', f"{chroot_dir}/proc"])
|
||||
subprocess.run(['mount', '--bind', '/dev', f"{chroot_dir}/dev"])
|
||||
|
||||
# Update package lists
|
||||
cmd = ['chroot', str(chroot_dir), 'apt-get', 'update']
|
||||
subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
# Install build dependencies
|
||||
if config['build_dependencies']:
|
||||
cmd = ['chroot', str(chroot_dir), 'apt-get', 'install', '-y'] + config['build_dependencies']
|
||||
subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
# Unmount
|
||||
subprocess.run(['umount', f"{chroot_dir}/proc"])
|
||||
subprocess.run(['umount', f"{chroot_dir}/dev"])
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to install build dependencies: {e}")
|
||||
|
||||
def build_package(self, package_source: str, environment_name: str,
|
||||
build_options: Dict[str, Any] = None) -> BuildResult:
|
||||
"""Build a Debian package"""
|
||||
start_time = datetime.now()
|
||||
|
||||
try:
|
||||
env_config = self._get_environment_config(environment_name)
|
||||
if not env_config:
|
||||
raise ValueError(f"Environment {environment_name} not found")
|
||||
|
||||
# Setup build environment if needed
|
||||
if not self._environment_exists(environment_name):
|
||||
if not self.setup_build_environment(environment_name):
|
||||
raise RuntimeError(f"Failed to setup build environment {environment_name}")
|
||||
|
||||
# Build package
|
||||
build_log = self._build_package_internal(package_source, env_config, build_options)
|
||||
|
||||
# Collect artifacts
|
||||
artifacts = self._collect_build_artifacts(package_source, environment_name)
|
||||
|
||||
# Run tests if available
|
||||
tests_passed = self._run_package_tests(package_source, environment_name)
|
||||
|
||||
build_time = (datetime.now() - start_time).total_seconds()
|
||||
|
||||
return BuildResult(
|
||||
package_name=self._extract_package_name(package_source),
|
||||
version=self._extract_package_version(package_source),
|
||||
architecture=env_config['architecture'],
|
||||
suite=env_config['suite'],
|
||||
build_status='success',
|
||||
build_log=build_log,
|
||||
artifacts=artifacts,
|
||||
build_time=build_time,
|
||||
dependencies_resolved=True,
|
||||
tests_passed=tests_passed,
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Package build failed: {e}")
|
||||
build_time = (datetime.now() - start_time).total_seconds()
|
||||
|
||||
return BuildResult(
|
||||
package_name=self._extract_package_name(package_source),
|
||||
version='unknown',
|
||||
architecture='unknown',
|
||||
suite='unknown',
|
||||
build_status='failed',
|
||||
build_log=str(e),
|
||||
artifacts=[],
|
||||
build_time=build_time,
|
||||
dependencies_resolved=False,
|
||||
tests_passed=False,
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
def _build_package_internal(self, package_source: str, env_config: Dict[str, Any],
|
||||
build_options: Dict[str, Any]) -> str:
|
||||
"""Internal package building logic"""
|
||||
try:
|
||||
# Try sbuild first
|
||||
if self._check_command('sbuild'):
|
||||
return self._build_with_sbuild(package_source, env_config, build_options)
|
||||
# Fall back to pbuilder
|
||||
elif self._check_command('pbuilder'):
|
||||
return self._build_with_pbuilder(package_source, env_config, build_options)
|
||||
else:
|
||||
raise RuntimeError("No build tools available")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Build failed: {e}")
|
||||
raise
|
||||
|
||||
def _build_with_sbuild(self, package_source: str, env_config: Dict[str, Any],
|
||||
build_options: Dict[str, Any]) -> str:
|
||||
"""Build package using sbuild"""
|
||||
try:
|
||||
cmd = [
|
||||
'sbuild',
|
||||
'--dist', env_config['suite'],
|
||||
'--arch', env_config['architecture'],
|
||||
'--chroot', env_config['name']
|
||||
]
|
||||
|
||||
if build_options and build_options.get('verbose'):
|
||||
cmd.append('--verbose')
|
||||
|
||||
cmd.append(package_source)
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Sbuild failed: {result.stderr}")
|
||||
|
||||
return result.stdout
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Sbuild build failed: {e}")
|
||||
raise
|
||||
|
||||
def _build_with_pbuilder(self, package_source: str, env_config: Dict[str, Any],
|
||||
build_options: Dict[str, Any]) -> str:
|
||||
"""Build package using pbuilder"""
|
||||
try:
|
||||
cmd = [
|
||||
'pbuilder',
|
||||
'--build',
|
||||
'--configfile', f"/etc/pbuilderrc-{env_config['name']}",
|
||||
'--basetgz', f"/var/cache/pbuilder/{env_config['name']}-base.tgz",
|
||||
'--buildresult', f"/var/cache/pbuilder/result"
|
||||
]
|
||||
|
||||
if build_options and build_options.get('verbose'):
|
||||
cmd.append('--verbose')
|
||||
|
||||
cmd.append(package_source)
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Pbuilder failed: {result.stderr}")
|
||||
|
||||
return result.stdout
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Pbuilder build failed: {e}")
|
||||
raise
|
||||
|
||||
def _collect_build_artifacts(self, package_source: str, environment_name: str) -> List[str]:
|
||||
"""Collect build artifacts"""
|
||||
artifacts = []
|
||||
|
||||
try:
|
||||
# Look for .deb files
|
||||
package_name = self._extract_package_name(package_source)
|
||||
deb_files = list(Path('.').glob(f"{package_name}*.deb"))
|
||||
artifacts.extend([str(f) for f in deb_files])
|
||||
|
||||
# Look for source packages
|
||||
dsc_files = list(Path('.').glob(f"{package_name}*.dsc"))
|
||||
artifacts.extend([str(f) for f in dsc_files])
|
||||
|
||||
# Look for build logs
|
||||
log_files = list(Path('.').glob(f"{package_name}*.build"))
|
||||
artifacts.extend([str(f) for f in log_files])
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to collect artifacts: {e}")
|
||||
|
||||
return artifacts
|
||||
|
||||
def _run_package_tests(self, package_source: str, environment_name: str) -> bool:
|
||||
"""Run package tests if available"""
|
||||
try:
|
||||
# Look for test suite
|
||||
test_dir = Path(package_source) / "debian" / "tests"
|
||||
if not test_dir.exists():
|
||||
return True # No tests to run
|
||||
|
||||
# Run tests using autopkgtest if available
|
||||
if self._check_command('autopkgtest'):
|
||||
cmd = ['autopkgtest', package_source, '--', 'schroot', environment_name]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Test execution failed: {e}")
|
||||
return False
|
||||
|
||||
def _extract_package_name(self, package_source: str) -> str:
|
||||
"""Extract package name from source"""
|
||||
try:
|
||||
# Try to read debian/control
|
||||
control_file = Path(package_source) / "debian" / "control"
|
||||
if control_file.exists():
|
||||
with open(control_file, 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('Package:'):
|
||||
return line.split(':', 1)[1].strip()
|
||||
|
||||
# Fallback to directory name
|
||||
return Path(package_source).name
|
||||
|
||||
except Exception:
|
||||
return Path(package_source).name
|
||||
|
||||
def _extract_package_version(self, package_source: str) -> str:
|
||||
"""Extract package version from source"""
|
||||
try:
|
||||
# Try to read debian/changelog
|
||||
changelog_file = Path(package_source) / "debian" / "changelog"
|
||||
if changelog_file.exists():
|
||||
with open(changelog_file, 'r') as f:
|
||||
first_line = f.readline().strip()
|
||||
if '(' in first_line and ')' in first_line:
|
||||
version_part = first_line.split('(')[1].split(')')[0]
|
||||
return version_part
|
||||
|
||||
return 'unknown'
|
||||
|
||||
except Exception:
|
||||
return 'unknown'
|
||||
|
||||
def list_build_environments(self) -> List[Dict[str, Any]]:
|
||||
"""List available build environments"""
|
||||
return self.build_environments["environments"]
|
||||
|
||||
def get_build_environment(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get build environment configuration"""
|
||||
return self._get_environment_config(name)
|
||||
|
||||
def main():
|
||||
"""Test build system integration"""
|
||||
build_system = DebianPackageBuildSystem()
|
||||
|
||||
# Check available tools
|
||||
tools = build_system.check_build_tools()
|
||||
print("Available build tools:")
|
||||
for tool, available in tools.items():
|
||||
status = "✓" if available else "✗"
|
||||
print(f" {status} {tool}")
|
||||
|
||||
# List build environments
|
||||
environments = build_system.list_build_environments()
|
||||
print(f"\nBuild environments: {len(environments)}")
|
||||
for env in environments:
|
||||
print(f" - {env['name']}: {env['suite']}/{env['architecture']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue