""" Custom exceptions for deb-mock This module provides a comprehensive exception hierarchy inspired by Mock's exception handling system, adapted for Debian-based build environments. """ import os import sys import functools from typing import Optional, Dict, Any, List class DebMockError(Exception): """ Base exception for all deb-mock errors. This is the root exception class that all other deb-mock exceptions inherit from. It provides common functionality for error reporting and recovery suggestions. """ def __init__(self, message: str, exit_code: int = 1, context: Optional[Dict[str, Any]] = None, suggestions: Optional[List[str]] = None): """ Initialize the exception with message and optional context. Args: message: Human-readable error message exit_code: Suggested exit code for CLI applications context: Additional context information for debugging suggestions: List of suggested actions to resolve the error """ super().__init__(message) self.message = message self.exit_code = exit_code self.context = context or {} self.suggestions = suggestions or [] def __str__(self) -> str: """Return formatted error message with context and suggestions.""" lines = [f"Error: {self.message}"] # Add context information if available if self.context: lines.append("\nContext:") for key, value in self.context.items(): lines.append(f" {key}: {value}") # Add suggestions if available if self.suggestions: lines.append("\nSuggestions:") for i, suggestion in enumerate(self.suggestions, 1): lines.append(f" {i}. {suggestion}") return "\n".join(lines) def print_error(self, file=sys.stderr) -> None: """Print formatted error message to specified file.""" print(str(self), file=file) def get_exit_code(self) -> int: """Get the suggested exit code for this error.""" return self.exit_code class ConfigurationError(DebMockError): """ Raised when there's an error in configuration. This exception is raised when configuration files are invalid, missing required options, or contain conflicting settings. """ def __init__(self, message: str, config_file: Optional[str] = None, config_section: Optional[str] = None): context = {} if config_file: context['config_file'] = config_file if config_section: context['config_section'] = config_section suggestions = [ "Check the configuration file syntax", "Verify all required options are set", "Ensure configuration values are valid for your system" ] super().__init__(message, exit_code=2, context=context, suggestions=suggestions) class ChrootError(DebMockError): """ Raised when there's an error with chroot operations. This exception covers chroot creation, management, and cleanup errors. """ def __init__(self, message: str, chroot_name: Optional[str] = None, operation: Optional[str] = None, chroot_path: Optional[str] = None): context = {} if chroot_name: context['chroot_name'] = chroot_name if operation: context['operation'] = operation if chroot_path: context['chroot_path'] = chroot_path suggestions = [ "Ensure you have sufficient disk space", "Check that you have root privileges for chroot operations", "Verify the chroot name is valid", "Try cleaning up existing chroots with 'deb-mock clean-chroot'" ] super().__init__(message, exit_code=3, context=context, suggestions=suggestions) class SbuildError(DebMockError): """ Raised when there's an error with sbuild operations. This exception covers sbuild execution, configuration, and result processing. """ def __init__(self, message: str, sbuild_config: Optional[str] = None, build_log: Optional[str] = None, return_code: Optional[int] = None): context = {} if sbuild_config: context['sbuild_config'] = sbuild_config if build_log: context['build_log'] = build_log if return_code is not None: context['return_code'] = return_code suggestions = [ "Check the build log for detailed error information", "Verify that sbuild is properly configured", "Ensure all build dependencies are available", "Try updating the chroot with 'deb-mock update-chroot'" ] super().__init__(message, exit_code=4, context=context, suggestions=suggestions) class BuildError(DebMockError): """ Raised when a build fails. This exception is raised when package building fails due to compilation errors, missing dependencies, or other build issues. """ def __init__(self, message: str, source_package: Optional[str] = None, build_log: Optional[str] = None, artifacts: Optional[List[str]] = None): context = {} if source_package: context['source_package'] = source_package if build_log: context['build_log'] = build_log if artifacts: context['artifacts'] = artifacts suggestions = [ "Review the build log for specific error messages", "Check that all build dependencies are installed", "Verify the source package is valid and complete", "Try building with verbose output: 'deb-mock --verbose build'" ] super().__init__(message, exit_code=5, context=context, suggestions=suggestions) class DependencyError(DebMockError): """ Raised when there are dependency issues. This exception covers missing build dependencies, version conflicts, and other dependency-related problems. """ def __init__(self, message: str, missing_packages: Optional[List[str]] = None, conflicting_packages: Optional[List[str]] = None): context = {} if missing_packages: context['missing_packages'] = missing_packages if conflicting_packages: context['conflicting_packages'] = conflicting_packages suggestions = [ "Install missing build dependencies", "Resolve package conflicts by updating or removing conflicting packages", "Check that your chroot has access to the required repositories", "Try updating the chroot: 'deb-mock update-chroot'" ] super().__init__(message, exit_code=6, context=context, suggestions=suggestions) class MetadataError(DebMockError): """ Raised when there's an error with metadata handling. This exception covers metadata capture, storage, and retrieval errors. """ def __init__(self, message: str, metadata_file: Optional[str] = None, operation: Optional[str] = None): context = {} if metadata_file: context['metadata_file'] = metadata_file if operation: context['operation'] = operation suggestions = [ "Check that the metadata directory is writable", "Verify that the metadata file format is valid", "Ensure sufficient disk space for metadata storage" ] super().__init__(message, exit_code=7, context=context, suggestions=suggestions) class CacheError(DebMockError): """ Raised when there's an error with cache operations. This exception covers root cache, package cache, and ccache errors. """ def __init__(self, message: str, cache_type: Optional[str] = None, cache_path: Optional[str] = None, operation: Optional[str] = None): context = {} if cache_type: context['cache_type'] = cache_type if cache_path: context['cache_path'] = cache_path if operation: context['operation'] = operation suggestions = [ "Check that cache directories are writable", "Ensure sufficient disk space for cache operations", "Try cleaning up old caches: 'deb-mock cleanup-caches'", "Verify cache configuration settings" ] super().__init__(message, exit_code=8, context=context, suggestions=suggestions) class PluginError(DebMockError): """ Raised when there's an error with plugin operations. This exception covers plugin loading, configuration, and execution errors. """ def __init__(self, message: str, plugin_name: Optional[str] = None, plugin_config: Optional[Dict[str, Any]] = None): context = {} if plugin_name: context['plugin_name'] = plugin_name if plugin_config: context['plugin_config'] = plugin_config suggestions = [ "Check that the plugin is properly installed", "Verify plugin configuration is valid", "Ensure plugin dependencies are satisfied", "Try disabling the plugin if it's causing issues" ] super().__init__(message, exit_code=9, context=context, suggestions=suggestions) class NetworkError(DebMockError): """ Raised when there are network-related errors. This exception covers repository access, package downloads, and other network operations. """ def __init__(self, message: str, url: Optional[str] = None, proxy: Optional[str] = None, timeout: Optional[int] = None): context = {} if url: context['url'] = url if proxy: context['proxy'] = proxy if timeout: context['timeout'] = timeout suggestions = [ "Check your internet connection", "Verify repository URLs are accessible", "Configure proxy settings if behind a firewall", "Try using a different mirror or repository" ] super().__init__(message, exit_code=10, context=context, suggestions=suggestions) class PermissionError(DebMockError): """ Raised when there are permission-related errors. This exception covers insufficient privileges for chroot operations, file access, and other permission issues. """ def __init__(self, message: str, operation: Optional[str] = None, path: Optional[str] = None, required_privileges: Optional[str] = None): context = {} if operation: context['operation'] = operation if path: context['path'] = path if required_privileges: context['required_privileges'] = required_privileges suggestions = [ "Run the command with appropriate privileges (sudo)", "Check file and directory permissions", "Verify your user is in the required groups", "Ensure the target paths are writable" ] super().__init__(message, exit_code=11, context=context, suggestions=suggestions) class ValidationError(DebMockError): """ Raised when input validation fails. This exception covers validation of source packages, configuration, and other input data. """ def __init__(self, message: str, field: Optional[str] = None, value: Optional[str] = None, expected_format: Optional[str] = None): context = {} if field: context['field'] = field if value: context['value'] = value if expected_format: context['expected_format'] = expected_format suggestions = [ "Check the input format and syntax", "Verify that required fields are provided", "Ensure values are within acceptable ranges", "Review the documentation for correct usage" ] super().__init__(message, exit_code=12, context=context, suggestions=suggestions) # Convenience functions for common error patterns def handle_exception(func): """ Decorator to handle exceptions and provide consistent error reporting. This decorator catches DebMockError exceptions and provides formatted error output with suggestions for resolution. """ @functools.wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except DebMockError as e: e.print_error() sys.exit(e.get_exit_code()) except Exception as e: # Convert unexpected exceptions to DebMockError error = DebMockError( f"Unexpected error: {str(e)}", context={'exception_type': type(e).__name__}, suggestions=[ "This may be a bug in deb-mock", "Check the logs for more details", "Report the issue with full error context" ] ) error.print_error() sys.exit(1) return wrapper def format_error_context(**kwargs) -> Dict[str, Any]: """ Helper function to format error context information. Args: **kwargs: Key-value pairs for context information Returns: Formatted context dictionary """ return {k: v for k, v in kwargs.items() if v is not None}