deb-mock/deb_mock/cli.py
robojerk c51819c836
Some checks failed
Build Deb-Mock Package / build (push) Failing after 1m9s
Lint Code / Lint All Code (push) Failing after 1s
Test Deb-Mock Build / test (push) Failing after 35s
Add comprehensive testing framework, performance monitoring, and plugin system
- Add complete pytest testing framework with conftest.py and test files
- Add performance monitoring and benchmarking capabilities
- Add plugin system with ccache plugin example
- Add comprehensive documentation (API, deployment, testing, etc.)
- Add Docker API wrapper for service deployment
- Add advanced configuration examples
- Remove old wget package file
- Update core modules with enhanced functionality
2025-08-19 20:49:32 -07:00

1437 lines
48 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Command-line interface for deb-mock
"""
import sys
import os
import click
from .config import Config
from .configs import get_available_configs, load_config
from .core import DebMock
from .exceptions import ConfigurationError, ValidationError, handle_exception
@click.group()
@click.version_option()
@click.option("--config", "-c", type=click.Path(exists=True), help="Configuration file path")
@click.option("--chroot", "-r", help="Chroot configuration name (e.g., debian-bookworm-amd64)")
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
@click.option("--debug", is_flag=True, help="Enable debug output")
@click.pass_context
def main(ctx, config, chroot, verbose, debug):
"""
Deb-Mock: A low-level utility to create clean, isolated build environments for Debian packages.
This tool is a direct functional replacement for Fedora's Mock, adapted specifically
for Debian-based ecosystems.
"""
ctx.ensure_object(dict)
ctx.obj["verbose"] = verbose
ctx.obj["debug"] = debug
# Load configuration
if config:
try:
ctx.obj["config"] = Config.from_file(config)
except ConfigurationError as e:
e.print_error()
sys.exit(e.get_exit_code())
elif chroot:
# Load core config by name (similar to Mock's -r option)
try:
config_data = load_config(chroot)
ctx.obj["config"] = Config(**config_data)
except ValueError as e:
error = ValidationError(
f"Invalid chroot configuration: {e}",
field="chroot",
value=chroot,
expected_format="debian-suite-arch or ubuntu-suite-arch",
)
error.print_error()
click.echo(f"Available configs: {', '.join(get_available_configs())}")
sys.exit(error.get_exit_code())
else:
ctx.obj["config"] = Config.default()
@main.command()
@click.argument("source_package", type=click.Path(exists=True))
@click.option("--chroot", help="Chroot environment to use")
@click.option("--arch", help="Target architecture")
@click.option("--output-dir", "-o", type=click.Path(), help="Output directory for build artifacts")
@click.option("--keep-chroot", is_flag=True, help="Keep chroot after build (for debugging)")
@click.option("--no-check", is_flag=True, help="Skip running tests during build")
@click.option("--offline", is_flag=True, help="Build in offline mode (no network access)")
@click.option("--build-timeout", type=int, help="Build timeout in seconds")
@click.option("--force-arch", help="Force target architecture")
@click.option("--unique-ext", help="Unique extension for buildroot directory")
@click.option("--config-dir", help="Configuration directory")
@click.option("--cleanup-after", is_flag=True, help="Clean chroot after build")
@click.option("--no-cleanup-after", is_flag=True, help="Don't clean chroot after build")
@click.pass_context
@handle_exception
def build(
ctx,
source_package,
chroot,
arch,
output_dir,
keep_chroot,
no_check,
offline,
build_timeout,
force_arch,
unique_ext,
config_dir,
cleanup_after,
no_cleanup_after,
):
"""
Build a Debian source package in an isolated environment.
SOURCE_PACKAGE: Path to the .dsc file or source package directory
"""
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if arch:
ctx.obj["config"].architecture = arch
if output_dir:
ctx.obj["config"].output_dir = output_dir
if keep_chroot:
ctx.obj["config"].keep_chroot = keep_chroot
if no_check:
ctx.obj["config"].run_tests = False
if offline:
ctx.obj["config"].enable_network = False
if build_timeout:
ctx.obj["config"].build_timeout = build_timeout
if force_arch:
ctx.obj["config"].force_architecture = force_arch
if unique_ext:
ctx.obj["config"].unique_extension = unique_ext
if config_dir:
ctx.obj["config"].config_dir = config_dir
if cleanup_after is not None:
ctx.obj["config"].cleanup_after = cleanup_after
if no_cleanup_after is not None:
ctx.obj["config"].cleanup_after = not no_cleanup_after
result = deb_mock.build(source_package)
if ctx.obj["verbose"]:
click.echo(f"Build completed successfully: {result}")
else:
click.echo("Build completed successfully")
@main.command()
@click.argument("source_packages", nargs=-1, type=click.Path(exists=True))
@click.option("--chroot", help="Chroot environment to use")
@click.option("--max-workers", type=int, help="Maximum number of parallel workers")
@click.option("--arch", help="Target architecture")
@click.option("--output-dir", "-o", type=click.Path(), help="Output directory for build artifacts")
@click.option("--keep-chroot", is_flag=True, help="Keep chroots after build (for debugging)")
@click.option("--no-check", is_flag=True, help="Skip running tests during build")
@click.option("--offline", is_flag=True, help="Build in offline mode (no network access)")
@click.option("--build-timeout", type=int, help="Build timeout in seconds")
@click.option("--force-arch", help="Force target architecture")
@click.option("--cleanup-after", is_flag=True, help="Clean chroots after build")
@click.option("--no-cleanup-after", is_flag=True, help="Don't clean chroots after build")
@click.pass_context
@handle_exception
def build_parallel(
ctx,
source_packages,
chroot,
max_workers,
arch,
output_dir,
keep_chroot,
no_check,
offline,
build_timeout,
force_arch,
cleanup_after,
no_cleanup_after,
):
"""
Build multiple Debian source packages in parallel using separate chroots.
SOURCE_PACKAGES: One or more paths to .dsc files or source package directories
"""
if not source_packages:
click.echo("Error: No source packages specified", err=True)
sys.exit(1)
if len(source_packages) == 1:
click.echo("Warning: Only one package specified, consider using 'build' instead", err=True)
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if arch:
ctx.obj["config"].architecture = arch
if output_dir:
ctx.obj["config"].output_dir = output_dir
if keep_chroot:
ctx.obj["config"].keep_chroot = True
if build_timeout:
ctx.obj["config"].parallel_build_timeout = build_timeout
if force_arch:
ctx.obj["config"].architecture = force_arch
if cleanup_after:
ctx.obj["config"].parallel_build_cleanup = True
if no_cleanup_after:
ctx.obj["config"].parallel_build_cleanup = False
# Build options
build_kwargs = {}
if no_check:
build_kwargs["no_check"] = True
if offline:
build_kwargs["offline"] = True
click.echo(f"Building {len(source_packages)} packages in parallel...")
try:
results = deb_mock.build_parallel(
list(source_packages),
max_workers=max_workers,
**build_kwargs
)
# Display results summary
successful = sum(1 for r in results if r.get("success", False))
failed = len(results) - successful
click.echo(f"\n=== Parallel Build Results ===")
click.echo(f"Total packages: {len(results)}")
click.echo(f"Successful: {successful}")
click.echo(f"Failed: {failed}")
if failed > 0:
click.echo(f"\nFailed packages:")
for i, result in enumerate(results):
if not result.get("success", False):
click.echo(f" {i+1}. {result.get('package_name', 'unknown')}: {result.get('error', 'Unknown error')}")
sys.exit(1)
else:
click.echo(f"\n✅ All packages built successfully!")
except Exception as e:
click.echo(f"Error during parallel build: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("source_packages", nargs=-1, type=click.Path(exists=True))
@click.option("--chroot", help="Chroot environment to use")
@click.option("--arch", help="Target architecture")
@click.option("--output-dir", "-o", type=click.Path(), help="Output directory for build artifacts")
@click.option("--keep-chroot", is_flag=True, help="Keep chroot after build (for debugging)")
@click.option(
"--continue-on-failure",
is_flag=True,
help="Continue building remaining packages even if one fails",
)
@click.pass_context
@handle_exception
def chain(ctx, source_packages, chroot, arch, output_dir, keep_chroot, continue_on_failure):
"""
Build a chain of packages that depend on each other.
SOURCE_PACKAGES: List of .dsc files or source package directories to build in order
"""
if not source_packages:
raise ValidationError(
"No source packages specified",
field="source_packages",
expected_format="list of .dsc files or source directories",
)
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if arch:
ctx.obj["config"].architecture = arch
if output_dir:
ctx.obj["config"].output_dir = output_dir
if keep_chroot:
ctx.obj["config"].keep_chroot = keep_chroot
results = deb_mock.build_chain(list(source_packages), continue_on_failure=continue_on_failure)
# Display results
for result in results:
if result["success"]:
click.echo(f"{result['package']} (step {result['order']})")
else:
click.echo(f"{result['package']} (step {result['order']}): {result['error']}")
# Check if all builds succeeded
failed_builds = [r for r in results if not r["success"]]
if failed_builds:
sys.exit(1)
else:
click.echo("All packages built successfully")
@main.command()
@click.argument("chroot_name")
@click.option("--arch", help="Target architecture")
@click.option("--suite", help="Debian suite (e.g., bookworm, sid)")
@click.option(
"--bootstrap",
is_flag=True,
help="Use bootstrap chroot for cross-distribution builds",
)
@click.option("--bootstrap-chroot", help="Name of bootstrap chroot to use")
@click.pass_context
@handle_exception
def init_chroot(ctx, chroot_name, arch, suite, bootstrap, bootstrap_chroot):
"""
Initialize a new chroot environment for building.
CHROOT_NAME: Name of the chroot environment to create
The --bootstrap option is useful for building packages for newer distributions
on older systems (e.g., building Debian Sid packages on Debian Stable).
"""
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if arch:
ctx.obj["config"].architecture = arch
if suite:
ctx.obj["config"].suite = suite
if bootstrap:
ctx.obj["config"].use_bootstrap_chroot = True
if bootstrap_chroot:
ctx.obj["config"].bootstrap_chroot_name = bootstrap_chroot
deb_mock.init_chroot(chroot_name)
click.echo(f"Chroot '{chroot_name}' initialized successfully")
if bootstrap:
click.echo("Bootstrap chroot was used for cross-distribution compatibility")
@main.command()
@click.argument("chroot_name")
@click.pass_context
@handle_exception
def clean_chroot(ctx, chroot_name):
"""
Clean up a chroot environment.
CHROOT_NAME: Name of the chroot environment to clean
"""
deb_mock = DebMock(ctx.obj["config"])
deb_mock.clean_chroot(chroot_name)
click.echo(f"Chroot '{chroot_name}' cleaned successfully")
@main.command()
@click.argument("chroot_name")
@click.pass_context
@handle_exception
def scrub_chroot(ctx, chroot_name):
"""
Clean up a chroot environment without removing it.
CHROOT_NAME: Name of the chroot environment to scrub
"""
deb_mock = DebMock(ctx.obj["config"])
deb_mock.chroot_manager.scrub_chroot(chroot_name)
click.echo(f"Chroot '{chroot_name}' scrubbed successfully")
@main.command()
@click.pass_context
@handle_exception
def scrub_all_chroots(ctx):
"""
Clean up all chroot environments without removing them.
"""
deb_mock = DebMock(ctx.obj["config"])
deb_mock.chroot_manager.scrub_all_chroots()
click.echo("All chroots scrubbed successfully")
@main.command()
@click.option("--chroot", help="Chroot environment to use")
@click.option("--preserve-env", is_flag=True, help="Preserve environment variables in chroot")
@click.option("--env-var", multiple=True, help="Specific environment variable to preserve")
@click.pass_context
@handle_exception
def shell(ctx, chroot, preserve_env, env_var):
"""
Open a shell in the chroot environment.
Use --preserve-env to preserve environment variables (addresses common
environment variable issues in chroot environments).
"""
deb_mock = DebMock(ctx.obj["config"])
chroot_name = chroot or ctx.obj["config"].chroot_name
# Configure environment preservation
if preserve_env:
ctx.obj["config"].environment_sanitization = False
if env_var:
ctx.obj["config"].preserve_environment.extend(env_var)
deb_mock.shell(chroot_name)
@main.command()
@click.argument("source_path")
@click.argument("dest_path")
@click.option("--chroot", help="Chroot environment to use")
@click.pass_context
@handle_exception
def copyin(ctx, source_path, dest_path, chroot):
"""
Copy files from host to chroot.
SOURCE_PATH: Path to file/directory on host
DEST_PATH: Path in chroot where to copy
"""
deb_mock = DebMock(ctx.obj["config"])
chroot_name = chroot or ctx.obj["config"].chroot_name
deb_mock.copyin(source_path, dest_path, chroot_name)
click.echo(f"Copied {source_path} to {dest_path} in chroot '{chroot_name}'")
@main.command()
@click.argument("source_path")
@click.argument("dest_path")
@click.option("--chroot", help="Chroot environment to use")
@click.pass_context
@handle_exception
def copyout(ctx, source_path, dest_path, chroot):
"""
Copy files from chroot to host.
SOURCE_PATH: Path to file/directory in chroot
DEST_PATH: Path on host where to copy
"""
deb_mock = DebMock(ctx.obj["config"])
chroot_name = chroot or ctx.obj["config"].chroot_name
deb_mock.copyout(source_path, dest_path, chroot_name)
click.echo(f"Copied {source_path} from chroot '{chroot_name}' to {dest_path}")
@main.command()
@click.pass_context
@handle_exception
def list_chroots(ctx):
"""
List available chroot environments.
"""
deb_mock = DebMock(ctx.obj["config"])
chroots = deb_mock.list_chroots()
if not chroots:
click.echo("No chroot environments found")
return
click.echo("Available chroot environments:")
for chroot in chroots:
click.echo(f" - {chroot}")
@main.command()
@click.pass_context
@handle_exception
def list_configs(ctx):
"""
List available core configurations.
"""
from .configs import list_configs
configs = list_configs()
if not configs:
click.echo("No core configurations found")
return
click.echo("Available core configurations:")
for config_name, config_info in configs.items():
click.echo(f" - {config_name}: {config_info['description']}")
click.echo(f" Suite: {config_info['suite']}, Arch: {config_info['architecture']}")
@main.command()
@click.pass_context
@handle_exception
def cleanup_caches(ctx):
"""
Clean up old cache files (similar to Mock's cache management).
"""
deb_mock = DebMock(ctx.obj["config"])
cleaned = deb_mock.cleanup_caches()
if not cleaned:
click.echo("No old cache files found to clean")
return
click.echo("Cleaned up cache files:")
for cache_type, count in cleaned.items():
if count > 0:
click.echo(f" - {cache_type}: {count} files")
@main.command()
@click.pass_context
@handle_exception
def cache_stats(ctx):
"""
Show cache statistics.
"""
deb_mock = DebMock(ctx.obj["config"])
stats = deb_mock.get_cache_stats()
if not stats:
click.echo("No cache statistics available")
return
click.echo("Cache Statistics:")
for cache_type, cache_stats in stats.items():
click.echo(f" - {cache_type}:")
if isinstance(cache_stats, dict):
for key, value in cache_stats.items():
click.echo(f" {key}: {value}")
else:
click.echo(f" {cache_stats}")
@main.command()
@click.pass_context
@handle_exception
def config(ctx):
"""
Show current configuration.
"""
config = ctx.obj["config"]
click.echo("Current configuration:")
click.echo(f" Chroot name: {config.chroot_name}")
click.echo(f" Architecture: {config.architecture}")
click.echo(f" Suite: {config.suite}")
click.echo(f" Output directory: {config.output_dir}")
click.echo(f" Keep chroot: {config.keep_chroot}")
click.echo(f" Use root cache: {config.use_root_cache}")
click.echo(f" Use ccache: {config.use_ccache}")
click.echo(f" Parallel jobs: {config.parallel_jobs}")
@main.command()
@click.argument("source_package", type=click.Path(exists=True))
@click.option("--chroot", help="Chroot environment to use")
@click.option("--arch", help="Target architecture")
@click.pass_context
@handle_exception
def install_deps(ctx, source_package, chroot, arch):
"""
Install build dependencies for a Debian source package.
SOURCE_PACKAGE: Path to the .dsc file or source package directory
"""
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if arch:
ctx.obj["config"].architecture = arch
result = deb_mock.install_dependencies(source_package)
if ctx.obj["verbose"]:
click.echo(f"Dependencies installed successfully: {result}")
else:
click.echo("Dependencies installed successfully")
@main.command()
@click.argument("packages", nargs=-1, required=True)
@click.option("--chroot", help="Chroot environment to use")
@click.option("--arch", help="Target architecture")
@click.pass_context
@handle_exception
def install(ctx, packages, chroot, arch):
"""
Install packages in the chroot environment.
PACKAGES: List of packages to install
"""
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if arch:
ctx.obj["config"].architecture = arch
result = deb_mock.install_packages(packages)
if ctx.obj["verbose"]:
click.echo(f"Packages installed successfully: {result}")
else:
click.echo(f"Packages installed successfully: {', '.join(packages)}")
@main.command()
@click.argument("packages", nargs=-1)
@click.option("--chroot", help="Chroot environment to use")
@click.option("--arch", help="Target architecture")
@click.pass_context
@handle_exception
def update(ctx, packages, chroot, arch):
"""
Update packages in the chroot environment.
PACKAGES: List of packages to update (if empty, update all)
"""
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if arch:
ctx.obj["config"].architecture = arch
result = deb_mock.update_packages(packages)
if ctx.obj["verbose"]:
click.echo(f"Packages updated successfully: {result}")
else:
if packages:
click.echo(f"Packages updated successfully: {', '.join(packages)}")
else:
click.echo("All packages updated successfully")
@main.command()
@click.argument("packages", nargs=-1, required=True)
@click.option("--chroot", help="Chroot environment to use")
@click.option("--arch", help="Target architecture")
@click.pass_context
@handle_exception
def remove(ctx, packages, chroot, arch):
"""
Remove packages from the chroot environment.
PACKAGES: List of packages to remove
"""
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if arch:
ctx.obj["config"].architecture = arch
result = deb_mock.remove_packages(packages)
if ctx.obj["verbose"]:
click.echo(f"Packages removed successfully: {result}")
else:
click.echo(f"Packages removed successfully: {', '.join(packages)}")
@main.command()
@click.argument("command")
@click.option("--chroot", help="Chroot environment to use")
@click.option("--arch", help="Target architecture")
@click.pass_context
@handle_exception
def apt_cmd(ctx, command, chroot, arch):
"""
Execute APT command in the chroot environment.
COMMAND: APT command to execute (e.g., "update", "install package")
"""
deb_mock = DebMock(ctx.obj["config"])
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if arch:
ctx.obj["config"].architecture = arch
result = deb_mock.execute_apt_command(command)
if ctx.obj["verbose"]:
click.echo(f"APT command executed successfully: {result}")
else:
click.echo(f"APT command executed: {command}")
@main.command()
@click.option("--expand", is_flag=True, help="Show expanded configuration values")
@click.pass_context
@handle_exception
def debug_config(ctx, expand):
"""
Show detailed configuration information for debugging.
"""
config = ctx.obj["config"]
if expand:
# Show expanded configuration (with template values resolved)
click.echo("Expanded Configuration:")
config_dict = config.to_dict()
for key, value in config_dict.items():
click.echo(f" {key}: {value}")
else:
# Show configuration with template placeholders
click.echo("Configuration (with templates):")
click.echo(f" chroot_name: {config.chroot_name}")
click.echo(f" architecture: {config.architecture}")
click.echo(f" suite: {config.suite}")
click.echo(f" basedir: {config.basedir}")
click.echo(f" output_dir: {config.output_dir}")
click.echo(f" chroot_dir: {config.chroot_dir}")
click.echo(f" cache_dir: {config.cache_dir}")
click.echo(f" chroot_home: {config.chroot_home}")
# Show plugin configuration
if hasattr(config, "plugins") and config.plugins:
click.echo(" plugins:")
for plugin_name, plugin_config in config.plugins.items():
click.echo(f" {plugin_name}: {plugin_config}")
@main.command()
@click.argument("chroot_name")
@click.pass_context
@handle_exception
def list_mounts(ctx, chroot_name):
"""List all active mounts for a chroot"""
deb_mock = DebMock(ctx.obj["config"])
try:
mounts = deb_mock.chroot_manager.list_mounts(chroot_name)
if not mounts:
click.echo(f"No active mounts found for chroot '{chroot_name}'")
return
click.echo(f"Active mounts for chroot '{chroot_name}':")
for mount in mounts:
click.echo(f" {mount['type']}: {mount['path']}")
except Exception as e:
click.echo(f"Error listing mounts: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.pass_context
@handle_exception
def cleanup_mounts(ctx, chroot_name):
"""Clean up all mounts for a chroot"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo(f"Cleaning up mounts for chroot '{chroot_name}'...")
deb_mock.chroot_manager.cleanup_mounts(chroot_name)
click.echo(f"✅ Mounts cleaned up for chroot '{chroot_name}'")
except Exception as e:
click.echo(f"Error cleaning up mounts: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.option("--mount-proc/--no-mount-proc", default=True, help="Mount /proc in chroot")
@click.option("--mount-sys/--no-mount-sys", default=True, help="Mount /sys in chroot")
@click.option("--mount-dev/--no-mount-dev", default=True, help="Mount /dev in chroot")
@click.option("--mount-devpts/--no-mount-devpts", default=True, help="Mount /dev/pts in chroot")
@click.option("--mount-tmp/--no-mount-tmp", default=True, help="Mount /tmp in chroot")
@click.option("--mount-home/--no-mount-home", default=False, help="Mount /home in chroot")
@click.option("--use-tmpfs", is_flag=True, help="Use tmpfs for /tmp mount")
@click.option("--tmpfs-size", default="2G", help="Size for tmpfs mount")
@click.pass_context
@handle_exception
def setup_mounts(
ctx,
chroot_name,
mount_proc,
mount_sys,
mount_dev,
mount_devpts,
mount_tmp,
mount_home,
use_tmpfs,
tmpfs_size,
):
"""Setup advanced mount points for a chroot"""
deb_mock = DebMock(ctx.obj["config"])
# Update config with mount options
ctx.obj["config"].mount_proc = mount_proc
ctx.obj["config"].mount_sys = mount_sys
ctx.obj["config"].mount_dev = mount_dev
ctx.obj["config"].mount_devpts = mount_devpts
ctx.obj["config"].mount_tmp = mount_tmp
ctx.obj["config"].mount_home = mount_home
ctx.obj["config"].use_tmpfs = use_tmpfs
ctx.obj["config"].tmpfs_size = tmpfs_size
try:
click.echo(f"Setting up advanced mounts for chroot '{chroot_name}'...")
# Clean up existing mounts first
deb_mock.chroot_manager.cleanup_mounts(chroot_name)
# Setup new mounts
deb_mock.chroot_manager._setup_advanced_mounts(chroot_name)
click.echo(f"✅ Advanced mounts setup complete for chroot '{chroot_name}'")
# Show current mounts
mounts = deb_mock.chroot_manager.list_mounts(chroot_name)
if mounts:
click.echo(f"\nActive mounts:")
for mount in mounts:
click.echo(f" {mount['type']}: {mount['path']}")
except Exception as e:
click.echo(f"Error setting up mounts: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.argument("host_path", type=click.Path(exists=True))
@click.argument("chroot_path")
@click.option("--options", help="Mount options (e.g., ro,noexec)")
@click.pass_context
@handle_exception
def bind_mount(ctx, chroot_name, host_path, chroot_path, options):
"""Add a custom bind mount to a chroot"""
deb_mock = DebMock(ctx.obj["config"])
try:
# Create bind mount configuration
bind_mount_config = {
"host": host_path,
"chroot": chroot_path,
"options": options or ""
}
click.echo(f"Adding bind mount {host_path} -> {chroot_path} to chroot '{chroot_name}'...")
# Setup the bind mount
deb_mock.chroot_manager._setup_bind_mount(chroot_name, bind_mount_config)
click.echo(f"✅ Bind mount added successfully")
except Exception as e:
click.echo(f"Error adding bind mount: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.argument("chroot_path")
@click.option("--size", default="100M", help="Size for tmpfs mount")
@click.option("--options", help="Additional mount options")
@click.pass_context
@handle_exception
def tmpfs_mount(ctx, chroot_name, chroot_path, size, options):
"""Add a tmpfs mount to a chroot"""
deb_mock = DebMock(ctx.obj["config"])
try:
# Create tmpfs mount configuration
tmpfs_mount_config = {
"chroot": chroot_path,
"size": size,
"options": options or ""
}
click.echo(f"Adding tmpfs mount {chroot_path} (size: {size}) to chroot '{chroot_name}'...")
# Setup the tmpfs mount
deb_mock.chroot_manager._setup_tmpfs_mount(chroot_name, tmpfs_mount_config)
click.echo(f"✅ Tmpfs mount added successfully")
except Exception as e:
click.echo(f"Error adding tmpfs mount: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.pass_context
@handle_exception
def user_info(ctx, chroot_name):
"""Show UID/GID information for a chroot"""
deb_mock = DebMock(ctx.obj["config"])
try:
user_info = deb_mock.chroot_manager.uid_manager.get_user_info()
click.echo(f"=== UID/GID Information for chroot '{chroot_name}' ===")
click.echo(f"Current user: {user_info['current_user']} (UID: {user_info['current_uid']}, GID: {user_info['current_gid']})")
click.echo(f"Chroot user: {user_info['chroot_user']} (UID: {user_info['chroot_uid']}, GID: {user_info['chroot_gid']})")
click.echo(f"Chroot group: {user_info['chroot_group']}")
# Check if chroot user exists
chroot_path = os.path.join(ctx.obj["config"].chroot_dir, chroot_name)
if os.path.exists(chroot_path):
user_valid = deb_mock.chroot_manager.uid_manager.validate_chroot_user(chroot_path)
click.echo(f"Chroot user configured: {'✅ Yes' if user_valid else '❌ No'}")
else:
click.echo("Chroot does not exist")
except Exception as e:
click.echo(f"Error getting user info: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.argument("username")
@click.pass_context
@handle_exception
def copy_host_user(ctx, chroot_name, username):
"""Copy a user from the host system to a chroot"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo(f"Copying host user '{username}' to chroot '{chroot_name}'...")
chroot_path = os.path.join(ctx.obj["config"].chroot_dir, chroot_name)
if not os.path.exists(chroot_path):
click.echo(f"Error: Chroot '{chroot_name}' does not exist", err=True)
sys.exit(1)
deb_mock.chroot_manager.uid_manager.copy_host_user(chroot_path, username)
click.echo(f"✅ Successfully copied host user '{username}' to chroot")
except Exception as e:
click.echo(f"Error copying host user: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.pass_context
@handle_exception
def setup_users(ctx, chroot_name):
"""Setup users and permissions for a chroot"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo(f"Setting up users and permissions for chroot '{chroot_name}'...")
chroot_path = os.path.join(ctx.obj["config"].chroot_dir, chroot_name)
if not os.path.exists(chroot_path):
click.echo(f"Error: Chroot '{chroot_name}' does not exist", err=True)
sys.exit(1)
# Setup chroot users
deb_mock.chroot_manager._setup_chroot_users(chroot_name)
click.echo(f"✅ Users and permissions setup complete for chroot '{chroot_name}'")
except Exception as e:
click.echo(f"Error setting up users: {e}", err=True)
sys.exit(1)
@main.command()
@click.pass_context
@handle_exception
def plugin_info(ctx):
"""Show information about loaded plugins"""
deb_mock = DebMock(ctx.obj["config"])
try:
plugin_info = deb_mock.plugin_manager.get_plugin_info()
click.echo("=== Plugin Information ===")
click.echo(f"Total plugins configured: {plugin_info['total_plugins']}")
click.echo(f"Loaded plugins: {', '.join(plugin_info['loaded_plugins']) if plugin_info['loaded_plugins'] else 'None'}")
click.echo(f"Available hook stages: {', '.join(plugin_info['available_stages']) if plugin_info['available_stages'] else 'None'}")
click.echo(f"Plugin directory: {plugin_info['plugin_dir']}")
click.echo(f"API version: {plugin_info['api_version']}")
except Exception as e:
click.echo(f"Error getting plugin info: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("stage")
@click.pass_context
@handle_exception
def list_hooks(ctx, stage):
"""List hooks registered for a specific stage"""
deb_mock = DebMock(ctx.obj["config"])
try:
hooks = deb_mock.plugin_manager.get_hooks(stage)
if not hooks:
click.echo(f"No hooks registered for stage '{stage}'")
return
click.echo(f"=== Hooks for stage '{stage}' ===")
for i, hook in enumerate(hooks, 1):
click.echo(f"{i}. {hook.__name__} ({hook.__module__})")
except Exception as e:
click.echo(f"Error listing hooks: {e}", err=True)
sys.exit(1)
@main.command()
@click.pass_context
@handle_exception
def list_stages(ctx):
"""List all available hook stages"""
deb_mock = DebMock(ctx.obj["config"])
try:
stages = deb_mock.plugin_manager.list_stages()
if not stages:
click.echo("No hook stages available")
return
click.echo("=== Available Hook Stages ===")
for stage in stages:
click.echo(f"- {stage}")
except Exception as e:
click.echo(f"Error listing stages: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.pass_context
@handle_exception
def chroot_info(ctx, chroot_name):
"""Show information about a chroot"""
deb_mock = DebMock(ctx.obj["config"])
try:
info = deb_mock.sbuild_wrapper.get_chroot_info(chroot_name)
click.echo(f"=== Chroot Information: {chroot_name} ===")
click.echo(f"Status: {info['status']}")
click.echo(f"Architecture: {info['architecture'] or 'Unknown'}")
click.echo(f"Distribution: {info['distribution'] or 'Unknown'}")
if 'package_count' in info:
click.echo(f"Package count: {info['package_count']}")
except Exception as e:
click.echo(f"Error getting chroot info: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("chroot_name")
@click.pass_context
@handle_exception
def update_chroot(ctx, chroot_name):
"""Update a chroot to ensure it's current"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo(f"Updating chroot '{chroot_name}'...")
deb_mock.sbuild_wrapper.update_chroot(chroot_name)
click.echo(f"✅ Chroot '{chroot_name}' updated successfully")
except Exception as e:
click.echo(f"Error updating chroot: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("source_package", type=click.Path(exists=True))
@click.option("--chroot", help="Chroot environment to use")
@click.pass_context
@handle_exception
def check_deps(ctx, source_package, chroot):
"""Check build dependencies for a source package"""
deb_mock = DebMock(ctx.obj["config"])
try:
if chroot:
ctx.obj["config"].chroot_name = chroot
click.echo(f"Checking build dependencies for {source_package}...")
deps = deb_mock.sbuild_wrapper.check_dependencies(source_package)
if deps["satisfied"]:
click.echo("✅ All build dependencies are satisfied")
else:
click.echo("❌ Build dependencies are not satisfied")
if deps["missing"]:
click.echo(f"Missing dependencies: {', '.join(deps['missing'])}")
if deps["conflicts"]:
click.echo(f"Conflicting dependencies: {', '.join(deps['conflicts'])}")
except Exception as e:
click.echo(f"Error checking dependencies: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("dependencies", nargs=-1)
@click.option("--chroot", help="Chroot environment to use")
@click.pass_context
@handle_exception
def install_deps(ctx, dependencies, chroot):
"""Install build dependencies in a chroot"""
if not dependencies:
click.echo("Error: No dependencies specified", err=True)
sys.exit(1)
deb_mock = DebMock(ctx.obj["config"])
try:
if chroot:
ctx.obj["config"].chroot_name = chroot
click.echo(f"Installing build dependencies: {', '.join(dependencies)}")
deb_mock.sbuild_wrapper.install_build_dependencies(list(dependencies))
click.echo("✅ Build dependencies installed successfully")
except Exception as e:
click.echo(f"Error installing dependencies: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("source_package", type=click.Path(exists=True))
@click.option("--chroot", help="Chroot environment to use")
@click.option("--output-dir", "-o", type=click.Path(), help="Output directory for build artifacts")
@click.option("--verbose", is_flag=True, help="Verbose output")
@click.option("--debug", is_flag=True, help="Debug output")
@click.option("--keep-chroot", is_flag=True, help="Keep chroot after build")
@click.pass_context
@handle_exception
def build_with_sbuild(ctx, source_package, chroot, output_dir, verbose, debug, keep_chroot):
"""Build a Debian source package using sbuild"""
deb_mock = DebMock(ctx.obj["config"])
try:
# Override config with command line options
if chroot:
ctx.obj["config"].chroot_name = chroot
if output_dir:
ctx.obj["config"].output_dir = output_dir
if verbose:
ctx.obj["config"].verbose = True
if debug:
ctx.obj["config"].debug = True
if keep_chroot:
ctx.obj["config"].keep_chroot = True
click.echo(f"Building {source_package} with sbuild...")
# Check dependencies first
deps = deb_mock.sbuild_wrapper.check_dependencies(source_package)
if not deps["satisfied"]:
click.echo("⚠️ Build dependencies not satisfied. Attempting to install...")
if deps["missing"]:
deb_mock.sbuild_wrapper.install_build_dependencies(deps["missing"])
# Build the package
result = deb_mock.sbuild_wrapper.build_package(source_package)
if result["success"]:
click.echo("✅ Package built successfully!")
click.echo(f"Output directory: {result['output_dir']}")
if result["artifacts"]:
click.echo("Build artifacts:")
for artifact in result["artifacts"]:
click.echo(f" {artifact}")
else:
click.echo("❌ Package build failed")
sys.exit(1)
except Exception as e:
click.echo(f"Error building package: {e}", err=True)
sys.exit(1)
@main.command()
@click.pass_context
@handle_exception
def performance_summary(ctx):
"""Show performance summary and statistics"""
deb_mock = DebMock(ctx.obj["config"])
try:
summary = deb_mock.performance_monitor.get_performance_summary()
if not summary:
click.echo("No performance data available yet")
return
click.echo("=== Performance Summary ===")
click.echo(f"Total Operations: {summary.get('total_operations', 0)}")
click.echo(f"Total Duration: {summary.get('total_duration', 0):.2f}s")
click.echo(f"Average Duration: {summary.get('average_duration', 0):.2f}s")
click.echo(f"Active Operations: {summary.get('active_operations', 0)}")
# Operation statistics
if 'operation_stats' in summary:
click.echo("\n=== Operation Statistics ===")
for op_name, stats in summary['operation_stats'].items():
click.echo(f"{op_name}:")
click.echo(f" Count: {stats['count']}")
click.echo(f" Avg Duration: {stats['avg_duration']:.2f}s")
click.echo(f" Min Duration: {stats['min_duration']:.2f}s")
click.echo(f" Max Duration: {stats['max_duration']:.2f}s")
# System statistics
if 'system_stats' in summary:
click.echo("\n=== System Statistics ===")
for key, value in summary['system_stats'].items():
click.echo(f"{key}: {value:.2f}")
except Exception as e:
click.echo(f"Error getting performance summary: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("operation_name")
@click.option("--iterations", "-i", type=int, default=3, help="Number of benchmark iterations")
@click.option("--function", "-f", help="Function to benchmark (e.g., 'build', 'chroot_creation')")
@click.pass_context
@handle_exception
def benchmark(ctx, operation_name, iterations, function):
"""Benchmark an operation multiple times"""
deb_mock = DebMock(ctx.obj["config"])
try:
if not function:
click.echo("Error: Please specify a function to benchmark with --function", err=True)
sys.exit(1)
# Get the function to benchmark
if hasattr(deb_mock, function):
operation_func = getattr(deb_mock, function)
else:
click.echo(f"Error: Function '{function}' not found", err=True)
sys.exit(1)
click.echo(f"Benchmarking {operation_name} with {iterations} iterations...")
result = deb_mock.performance_monitor.benchmark_operation(
operation_name, operation_func, iterations
)
click.echo(f"\n=== Benchmark Results for {operation_name} ===")
click.echo(f"Iterations: {result['iterations']}")
click.echo(f"Average Duration: {result['average_duration']:.2f}s")
click.echo(f"Min Duration: {result['min_duration']:.2f}s")
click.echo(f"Max Duration: {result['max_duration']:.2f}s")
click.echo(f"Variance: {result['variance']:.4f}")
except Exception as e:
click.echo(f"Error during benchmarking: {e}", err=True)
sys.exit(1)
@main.command()
@click.option("--output-file", "-o", type=click.Path(), help="Output file for the report")
@click.pass_context
@handle_exception
def performance_report(ctx, output_file):
"""Generate a comprehensive performance report"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo("Generating performance report...")
report_file = deb_mock.performance_reporter.generate_performance_report(
deb_mock.performance_monitor, output_file
)
click.echo(f"✅ Performance report generated: {report_file}")
except Exception as e:
click.echo(f"Error generating performance report: {e}", err=True)
sys.exit(1)
@main.command()
@click.argument("build_id")
@click.option("--output-file", "-o", type=click.Path(), help="Output file for the report")
@click.pass_context
@handle_exception
def build_profile_report(ctx, build_id, output_file):
"""Generate a detailed build profile report"""
deb_mock = DebMock(ctx.obj["config"])
try:
# Find the build profile
profile = None
for profile_id, prof in deb_mock.performance_monitor._build_profiles.items():
if prof.build_id == build_id:
profile = prof
break
if not profile:
click.echo(f"Error: Build profile with ID '{build_id}' not found", err=True)
sys.exit(1)
click.echo(f"Generating build profile report for {profile.package_name}...")
report_file = deb_mock.performance_reporter.generate_build_profile_report(
profile, output_file
)
click.echo(f"✅ Build profile report generated: {report_file}")
except Exception as e:
click.echo(f"Error generating build profile report: {e}", err=True)
sys.exit(1)
@main.command()
@click.pass_context
@handle_exception
def performance_analysis(ctx):
"""Analyze performance and generate optimization suggestions"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo("Analyzing performance data...")
# Get all build profiles
profiles = list(deb_mock.performance_monitor._build_profiles.values())
if not profiles:
click.echo("No build profiles available for analysis")
return
click.echo(f"Found {len(profiles)} build profiles for analysis")
# Analyze each profile
for i, profile in enumerate(profiles, 1):
click.echo(f"\n=== Analysis {i}: {profile.package_name} ===")
analysis = deb_mock.performance_optimizer.analyze_build_performance(profile)
click.echo(f"Performance Score: {analysis['score']}/100")
if analysis['suggestions']:
click.echo("\nOptimization Suggestions:")
for suggestion in analysis['suggestions']:
click.echo(f"{suggestion}")
if analysis['automatic_tunings']:
click.echo("\nAutomatic Tuning Recommendations:")
for tuning in analysis['automatic_tunings']:
click.echo(f"{tuning['reason']}")
click.echo(f" Current: {tuning.get('current', 'N/A')}")
click.echo(f" Suggested: {tuning.get('suggested', 'N/A')}")
if analysis['manual_recommendations']:
click.echo("\nManual Optimization Recommendations:")
for recommendation in analysis['manual_recommendations']:
click.echo(f"{recommendation}")
click.echo("\n✅ Performance analysis completed")
except Exception as e:
click.echo(f"Error during performance analysis: {e}", err=True)
sys.exit(1)
@main.command()
@click.option("--auto-apply", is_flag=True, help="Automatically apply optimization tunings")
@click.pass_context
@handle_exception
def optimize(ctx, auto_apply):
"""Apply performance optimizations based on analysis"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo("Applying performance optimizations...")
# Get all build profiles
profiles = list(deb_mock.performance_monitor._build_profiles.values())
if not profiles:
click.echo("No build profiles available for optimization")
return
total_tunings = 0
applied_tunings = 0
for profile in profiles:
analysis = deb_mock.performance_optimizer.analyze_build_performance(profile)
total_tunings += len(analysis['automatic_tunings'])
if auto_apply and analysis['automatic_tunings']:
results = deb_mock.performance_optimizer.apply_automatic_tunings(
analysis['automatic_tunings']
)
applied_tunings += len(results['applied'])
if results['failed']:
click.echo(f"⚠️ Some tunings failed for {profile.package_name}")
click.echo(f"\n=== Optimization Summary ===")
click.echo(f"Total tunings available: {total_tunings}")
click.echo(f"Tunings applied: {applied_tunings}")
if auto_apply:
click.echo("✅ Automatic optimization completed")
else:
click.echo(" Use --auto-apply to automatically apply optimizations")
except Exception as e:
click.echo(f"Error during optimization: {e}", err=True)
sys.exit(1)
@main.command()
@click.option("--output-file", "-o", type=click.Path(), help="Output file for metrics export")
@click.pass_context
@handle_exception
def export_metrics(ctx, output_file):
"""Export performance metrics to a file"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo("Exporting performance metrics...")
export_file = deb_mock.performance_monitor.export_metrics(output_file)
click.echo(f"✅ Performance metrics exported to: {export_file}")
except Exception as e:
click.echo(f"Error exporting metrics: {e}", err=True)
sys.exit(1)
@main.command()
@click.pass_context
@handle_exception
def cleanup_metrics(ctx):
"""Clean up old performance metrics"""
deb_mock = DebMock(ctx.obj["config"])
try:
click.echo("Cleaning up old performance metrics...")
deb_mock.performance_monitor.cleanup_old_metrics()
click.echo("✅ Old performance metrics cleaned up")
except Exception as e:
click.echo(f"Error cleaning up metrics: {e}", err=True)
sys.exit(1)
if __name__ == "__main__":
main()