- 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
1437 lines
48 KiB
Python
1437 lines
48 KiB
Python
#!/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()
|