Services layer¶
Business logic services and orchestration for EzCompiler.
The services layer implements the core business logic, handling compilation, configuration, template processing, and upload operations.
CompilerService¶
Service for managing compiler selection and orchestrating the compilation process.
CompilerService
¶
CompilerService(config: CompilerConfig)
Compilation orchestration service.
Orchestrates project compilation using different compiler backends. Handles compiler selection, validation, and execution.
Attributes:
| Name | Type | Description |
|---|---|---|
_config |
CompilerConfig instance with project settings |
Example
config = CompilerConfig(...) service = CompilerService(config) result = service.compile(console=True, compiler="PyInstaller") print(result.zip_needed) False
Initialize the compiler service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
CompilerConfig instance with project settings |
required |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If config is None or invalid |
compiler_instance
property
¶
compiler_instance: CompilerPort | None
Get the current compiler instance.
Returns:
| Type | Description |
|---|---|
CompilerPort | None
|
CompilerPort | None: Current compiler instance or None if not compiled yet |
compile
¶
compile(
console: bool = True,
compiler: _CompilerName | None = None,
) -> CompilationResult
Compile the project using specified or auto-selected compiler.
Validates configuration, selects compiler if not specified, and executes compilation. Returns result with zip_needed flag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
console
|
bool
|
Whether to show console window (default: True) |
True
|
compiler
|
_CompilerName | None
|
Compiler to use or None for auto-selection - "Cx_Freeze": Creates directory with dependencies - "PyInstaller": Creates single executable - "Nuitka": Creates standalone folder or single executable - None: Use config compiler, or prompt user if none set |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
CompilationResult |
CompilationResult
|
Result with zip_needed flag and compiler instance |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If project not initialized |
CompilationError
|
If compilation fails |
Example
service = CompilerService(config) result = service.compile(console=False, compiler="PyInstaller") if result.zip_needed: ... # Create ZIP archive
use_existing_build
¶
use_existing_build(
compiler: _CompilerName | None = None,
) -> CompilationResult
Reuse a previous build from output_folder instead of compiling.
Resolves the compiler (without running it) so downstream stages (zip, installer, release) can resume from an already compiled output folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compiler
|
_CompilerName | None
|
Compiler used for the previous build, or None to use config.compiler (or prompt when unset) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
CompilationResult |
CompilationResult
|
Result describing the existing build |
Raises:
| Type | Description |
|---|---|
CompilationError
|
If output_folder is missing or empty, or the compiler name is invalid |
Example
service = CompilerService(config) result = service.use_existing_build(compiler="Cx_Freeze")
ConfigService¶
Service for loading, validating, and managing configuration.
ConfigService
¶
Configuration orchestration service.
Orchestrates configuration loading from multiple sources with a cascade merge system. Sources are merged in priority order (later wins):
- pyproject.toml [project] + [tool.ezcompiler] (base)
- ezcompiler.yaml or ezcompiler.json (override)
- CLI arguments (final override)
Example
config = ConfigService.build_compiler_config() config = ConfigService.build_compiler_config( ... config_path=Path("ezcompiler.yaml"), ... cli_overrides={"compiler": "PyInstaller"}, ... )
load_config
staticmethod
¶
load_config(
config_path: Path | None = None,
pyproject_path: Path | None = None,
cli_overrides: dict[str, Any] | None = None,
search_dir: Path | None = None,
) -> dict[str, Any]
Load configuration with full cascade.
Merges configuration from multiple sources in priority order: 1. pyproject.toml [project] + [tool.ezcompiler] (base layer) 2. ezcompiler.yaml or .json - explicit or auto-discovered (override) 3. CLI overrides (final override)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config_path
|
Path | None
|
Explicit config file path for YAML/JSON (skips auto-discovery) |
None
|
pyproject_path
|
Path | None
|
Explicit pyproject.toml path (default: search_dir/pyproject.toml) |
None
|
cli_overrides
|
dict[str, Any] | None
|
Dict of CLI-provided overrides (only non-default values) |
None
|
search_dir
|
Path | None
|
Directory to search for config files (default: cwd) |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: Merged configuration dictionary |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If no configuration source found or loading fails |
build_compiler_config
staticmethod
¶
build_compiler_config(
config_path: Path | None = None,
pyproject_path: Path | None = None,
cli_overrides: dict[str, Any] | None = None,
search_dir: Path | None = None,
) -> CompilerConfig
Load configuration and create a CompilerConfig instance.
Convenience method that combines load_config() with CompilerConfig.from_dict().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config_path
|
Path | None
|
Explicit config file path for YAML/JSON |
None
|
pyproject_path
|
Path | None
|
Explicit pyproject.toml path |
None
|
cli_overrides
|
dict[str, Any] | None
|
Dict of CLI-provided overrides |
None
|
search_dir
|
Path | None
|
Directory to search for config files (default: cwd) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
CompilerConfig |
CompilerConfig
|
Validated configuration instance |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If configuration is invalid or not found |
load_pyproject_as_dict
staticmethod
¶
Load and extract configuration from a pyproject.toml file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pyproject_path
|
Path
|
Path to the pyproject.toml file |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: Extracted configuration dictionary |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If the file cannot be loaded or parsed |
merge_configs
staticmethod
¶
Deep-merge two configuration dictionaries (override wins on conflicts).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base
|
dict[str, Any]
|
Base configuration dictionary |
required |
override
|
dict[str, Any]
|
Override dictionary (values win over base) |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: Merged configuration dictionary |
TemplateService¶
Service for processing templates and generating dynamic files.
TemplateService
¶
TemplateService(file_writer: BaseFileWriter | None = None)
Template processing and file generation service.
Orchestrates template loading, processing, and file generation for configuration files, build.py, and version information files.
Attributes:
| Name | Type | Description |
|---|---|---|
_template_loader |
TemplateLoader instance for template operations |
|
_processor |
TemplateProcessor instance for variable substitution |
Example
service = TemplateService() config = {"version": "1.0.0", "project_name": "MyApp"} service.generate_setup_file(config, Path("build.py")) service.generate_version_file(config, Path("version_info.txt"))
Initialize the template service.
Creates TemplateLoader and TemplateProcessor instances for template operations.
generate_config_file
¶
generate_config_file(
config: dict[str, Any],
output_path: Path,
format_type: str = "yaml",
) -> None
Generate a configuration file (YAML or JSON) from template.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Project configuration dictionary |
required |
output_path
|
Path
|
Path where to save the config file |
required |
format_type
|
str
|
Format type ("yaml" or "json") |
'yaml'
|
Raises:
| Type | Description |
|---|---|
TemplateError
|
If generation fails |
Example
config = {"version": "1.0.0", "project_name": "MyApp"} service.generate_config_file(config, Path("ezcompiler.yaml"), "yaml")
generate_setup_file
¶
generate_setup_file(
config: dict[str, Any],
output_path: Path | None = None,
output_dir: Path | None = None,
) -> Path
Generate a build.py file from template.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Project configuration dictionary |
required |
output_path
|
Path | None
|
Direct path to build.py file (optional) |
None
|
output_dir
|
Path | None
|
Directory where to save build.py (optional, defaults to current dir) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
Path to the generated build.py file |
Raises:
| Type | Description |
|---|---|
TemplateError
|
If generation fails |
Example
config = {"version": "1.0.0", "project_name": "MyApp"} setup_path = service.generate_setup_file(config, output_dir=Path("build"))
generate_version_file
¶
generate_version_file(
config: dict[str, Any],
output_path: Path | None = None,
format_type: str = "txt",
) -> Path
Generate a version information file from template.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Project configuration dictionary |
required |
output_path
|
Path | None
|
Path where to save the version file (optional) |
None
|
format_type
|
str
|
Template format type (default: "txt") |
'txt'
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
Path to the generated version file |
Raises:
| Type | Description |
|---|---|
VersionError
|
If generation fails |
Note
If output_path is None, uses version_filename and output_folder from config.
Example
config = { ... "version": "1.0.0", ... "project_name": "MyApp", ... "version_filename": "version_info.txt", ... "output_folder": "dist" ... } version_path = service.generate_version_file(config)
process_config_template
¶
Process a configuration template with values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
format_type
|
str
|
Format type ("yaml" or "json") |
required |
config
|
dict[str, Any]
|
Configuration dictionary |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Processed template content |
Example
config = {"version": "1.0.0", "project_name": "MyApp"} content = service.process_config_template("yaml", config)
process_setup_template
¶
Process a setup template with values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Configuration dictionary |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Processed template content |
Example
config = {"version": "1.0.0", "project_name": "MyApp"} content = service.process_setup_template(config)
process_version_template
¶
process_version_template(
version: str,
company_name: str,
project_description: str,
project_name: str,
format_type: str = "txt",
) -> str
Process a version template with values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
version
|
str
|
Project version |
required |
company_name
|
str
|
Company name |
required |
project_description
|
str
|
Project description |
required |
project_name
|
str
|
Project name |
required |
format_type
|
str
|
Format type (default: "txt") |
'txt'
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Processed template content |
Example
content = service.process_version_template( ... "1.0.0", "MyCompany", "Description", "MyApp" ... )
list_available_templates
¶
List all available templates.
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
dict[str, list[str]]: Dictionary mapping template types to available formats |
Example
templates = service.list_available_templates() print(templates)
validate_template
¶
Validate a template file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template_type
|
str
|
Type of template (config, version, setup) |
required |
format_type
|
str
|
Format of the template |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if template is valid, False otherwise |
Example
is_valid = service.validate_template("config", "yaml")
generate_mockup_template
¶
Generate a template file with mockup values instead of placeholders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template_type
|
str
|
Type of template (config, version, setup) |
required |
format_type
|
str
|
Format of the template (yaml, json, py, txt) |
required |
output_path
|
Path
|
Path where to save the generated file |
required |
Raises:
| Type | Description |
|---|---|
TemplateError
|
If generation fails |
Example
service.generate_mockup_template("config", "yaml", Path("ezcompiler.yaml"))
generate_raw_template
¶
Generate a raw template file with placeholders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template_type
|
str
|
Type of template (config, version, setup) |
required |
format_type
|
str
|
Format of the template (yaml, json, py, txt) |
required |
output_path
|
Path
|
Path where to save the template file |
required |
Raises:
| Type | Description |
|---|---|
TemplateError
|
If generation fails |
Example
service.generate_raw_template("config", "yaml", Path("template.yaml"))
PipelineService¶
Service for orchestrating the full compile → zip → upload pipeline with injectable factory.
PipelineService
¶
PipelineService(
compiler_service_factory: Callable[
[CompilerConfig], CompilerService
]
| None = None,
)
Service that coordinates compile, zip and upload stages.
Initialise the pipeline service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compiler_service_factory
|
Callable[[CompilerConfig], CompilerService] | None
|
Optional factory to create a CompilerService
from a CompilerConfig. Defaults to |
None
|
compile_project
¶
compile_project(
config: CompilerConfig,
console: bool = True,
compiler: str | None = None,
) -> tuple[CompilerService, CompilationResult]
Compile a project and return service + result.
reuse_build
¶
reuse_build(
config: CompilerConfig, compiler: str | None = None
) -> tuple[CompilerService, CompilationResult]
Reuse an existing build (skip compilation) and return service + result.
zip_artifact
¶
zip_artifact(
config: CompilerConfig,
compiler_service: CompilerService,
compilation_result: CompilationResult | None,
progress_callback: Callable[[str, int], None]
| None = None,
) -> bool
Create ZIP artifact when required and return True when created.
build_stages
staticmethod
¶
build_stages(
config: CompilerConfig,
should_zip: bool = False,
should_upload: bool = False,
should_release: bool = False,
should_installer: bool = False,
should_build: bool = True,
) -> list[dict[str, Any]]
Build the stage list for dynamic_layered_progress.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Compiler configuration (used for display labels) |
required |
should_zip
|
bool
|
Whether a ZIP stage should be included |
False
|
should_upload
|
bool
|
Whether an upload stage should be included |
False
|
should_release
|
bool
|
Whether a TUF release stage should be included |
False
|
should_installer
|
bool
|
Whether an installer stage should be included |
False
|
should_build
|
bool
|
Whether version + compile stages run; when False a single "reuse existing build" stage replaces them |
True
|
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
list[dict]: Stage configuration list ready for dynamic_layered_progress |
upload_artifact
¶
upload_artifact(
config: CompilerConfig,
structure: str,
destination: str,
compilation_result: CompilationResult | None,
upload_config: dict[str, Any] | None = None,
) -> None
Upload project artifact to a destination.
resolve_publishable_assets
staticmethod
¶
resolve_publishable_assets(
config: CompilerConfig,
) -> tuple[Path | None, Path | None]
Resolve the publishable artifacts, with the same contract everywhere.
Both publication paths (platform and files) rely on this resolution: an enabled but missing installer fails whatever the destination, instead of being published silently to disk/server/r2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Configuration (supplies installer, zip_file_path, paths). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[Path | None, Path | None]
|
|
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If the enabled installer is missing, or if no publishable artifact exists. |
assemble_release_dir
staticmethod
¶
assemble_release_dir(config: CompilerConfig) -> Path
Assemble the release directory holding the zip and the installer.
Layout (cleaned on every run)::
release/
├── <App>.zip (if the file exists)
└── <App>-<version>-setup.exe (if installer.enabled=True)
The TUF tree (metadata/ + targets/) stays in tufup_repo_dir and is pushed straight to the update backend by upload().
Artifact resolution is shared by both publication paths (see
resolve_publishable_assets): an enabled but missing installer
fails here too.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Configuration (supplies output_folder and zip_file_path). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
The assembled |
stage_versioned_assets
staticmethod
¶
stage_versioned_assets(
config: CompilerConfig,
) -> list[Path]
Resolve the publishable artifacts and version the zip name.
config.zip_file_path is dist/<Project>.zip — without a version.
Publishing that name as is would produce assets indistinguishable
between two releases, so the zip is copied to
<Project>-<version>.zip. The copy is idempotent: running the
method again changes nothing.
A project without an installer (installer.enabled = False) is a
legitimate case: the list then holds the zip alone. An enabled but
missing installer, however, is an error. The zip stays optional: a
single-file compilation does not produce one.
Versioned copies accumulate in dist/ (one per version); they are
never cleaned up automatically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Configuration (supplies project_name, version, paths). |
required |
Returns:
| Type | Description |
|---|---|
list[Path]
|
list[Path]: The existing assets — installer first, then zip. |
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If the enabled installer is missing, or if no publishable artifact exists. |
release_artifact
staticmethod
¶
release_artifact(
config: CompilerConfig,
compilation_result: CompilationResult | None,
*,
required: bool = False,
) -> Path
Build the local TUF repo from output_folder. Never publishes.
build_installer
staticmethod
¶
build_installer(
config: CompilerConfig,
compilation_result: CompilationResult | None,
) -> Path | None
Build the Inno Setup installer when installer.enabled=True.
UploaderService¶
Service for orchestrating upload operations to various backends.
UploaderService
¶
Upload orchestration service.
Orchestrates file and directory uploads using different upload backends. Handles upload type selection, validation, and execution.
Example
service = UploaderService() service.upload( ... source_path=Path("dist.zip"), ... upload_type="disk", ... destination="releases/", ... upload_config={"overwrite": True} ... )
upload
staticmethod
¶
upload(
source_path: Path,
upload_type: UploadType,
destination: str,
upload_config: dict[str, Any] | None = None,
) -> None
Upload a file or directory to the specified destination.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_path
|
Path
|
Path to the source file or directory |
required |
upload_type
|
UploadType
|
Type of upload ("disk" or "server") |
required |
destination
|
str
|
Destination path or URL |
required |
upload_config
|
dict[str, Any] | None
|
Additional uploader configuration options |
None
|
Raises:
| Type | Description |
|---|---|
UploadError
|
If upload fails or upload type is invalid |
Example
UploaderService.upload( ... Path("dist.zip"), ... "disk", ... "releases/", ... {"overwrite": True} ... )
download
staticmethod
¶
download(
remote_source: str,
upload_type: UploadType,
destination_local: Path,
upload_config: dict[str, Any] | None = None,
) -> None
Download a remote tree into destination_local via the backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
remote_source
|
str
|
Remote source (path, URL or prefix) to fetch. |
required |
upload_type
|
UploadType
|
Backend type ("disk", "server" or "r2"). |
required |
destination_local
|
Path
|
Local directory to populate. |
required |
upload_config
|
dict[str, Any] | None
|
Additional uploader configuration options. |
None
|
Raises:
| Type | Description |
|---|---|
UploadError
|
If the download fails or the type is invalid. |
upload_release
staticmethod
¶
upload_release(
config: CompilerConfig,
repo_dir: Path,
release_root: Path | None,
destination: str | None = None,
repo_destination: str | None = None,
release_destination: str | None = None,
upload_config: dict[str, Any] | None = None,
) -> None
Sequential double upload: TUF tree then installer zip.
Step 1 — upload the TUF tree to <dest>/update/ (or the R2 prefix).
Step 2 — upload the installer zip to <dest>/release/ (skipped on R2).
Note: The double upload is not atomic. If the zip upload fails, the TUF repo is already online. On failure, run upload() again to resume.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
CompilerConfig holding the destinations and R2 options. |
required |
repo_dir
|
Path
|
Local directory of the TUF repo. |
required |
release_root
|
Path | None
|
Local directory of the installer zip (None on R2). |
required |
destination
|
str | None
|
Shared override for both destinations. |
None
|
repo_destination
|
str | None
|
Override for |
None
|
release_destination
|
str | None
|
Override for |
None
|
upload_config
|
dict[str, Any] | None
|
Extra options passed to the uploaders. |
None
|
Raises:
| Type | Description |
|---|---|
UploadError
|
If an upload fails. |
upload_tuf_repo
staticmethod
¶
upload_tuf_repo(
config: CompilerConfig,
repo_dir: Path,
repo_dest: str,
destination: str | None,
upload_config: dict[str, Any] | None,
) -> None
Upload the public part of the TUF tree to the configured destination.
Only metadata/, targets/ and withdrawn.json are published,
from a temporary copy (see staged_tuf_tree): the private keys
stored under repo_dir are never transferred.
staged_tuf_tree
staticmethod
¶
Copy the public files of the TUF tree into a temporary directory.
The layout is preserved (metadata/, targets/,
withdrawn.json at the root); the directory is removed on exit.
The keystore is always excluded from the copy, even when it sits
under metadata/ or targets/.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
repo_dir
|
Path
|
Root of the local TUF tree. |
required |
keys_dir
|
Path | None
|
Private keystore to exclude, on top of any directory
named |
None
|
Raises:
| Type | Description |
|---|---|
UploadError
|
If |
upload_release_zip
staticmethod
¶
upload_release_zip(
config: CompilerConfig,
release_root: Path,
rel_dest: str,
destination: str | None,
upload_config: dict[str, Any] | None,
) -> None
Upload the installer zip to the configured destination.
get_supported_types
staticmethod
¶
Get list of supported upload types.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: List of supported upload type names |
Example
types = UploaderService.get_supported_types() print(types) ['disk', 'server']
validate_upload_config
staticmethod
¶
Validate configuration for a specific upload type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
upload_type
|
UploadType
|
Type of uploader to validate |
required |
config
|
dict[str, Any] | None
|
Configuration to validate (default: None) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if configuration is valid, False otherwise |
Example
is_valid = UploaderService.validate_upload_config( ... "disk", {"overwrite": True} ... )
UpdaterService¶
Service for generating client updater files (update.py, settings.py, root.json) for tufup-based auto-update workflows.
UpdaterService
¶
Service that generates the tufup client update files.
generate
staticmethod
¶
generate(
config: CompilerConfig, output_dir: Path
) -> list[Path]
Generate update.py, settings.py, and copy root.json.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Compiler configuration (must have tuf_enabled=True). |
required |
output_dir
|
Path
|
Directory where files are written (created if absent). |
required |
Returns:
| Type | Description |
|---|---|
list[Path]
|
List of generated file paths [settings.py, update.py, root.json]. |
Raises:
| Type | Description |
|---|---|
UpdaterConfigError
|
If config is invalid (tuf_enabled=False, root.json absent, repo_public_url missing for non-disk). |
UpdaterGenerationError
|
If writing files fails. |
InstallerService¶
Service orchestrating first-deployment installer packaging via an installer adapter.
InstallerService
¶
Service orchestrating first-deployment installer packaging.
build_installer
staticmethod
¶
build_installer(
bundle_dir: Path,
app_name: str,
version: str,
output_dir: Path,
*,
installer_type: str = "innosetup",
installer_config: InstallerConfig | None = None,
company_name: str = "",
icon: str = "",
main_file: str = "",
) -> Path
Build the installer executable for a compiled bundle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle_dir
|
Path
|
Directory containing the compiled application. |
required |
app_name
|
str
|
Application name. |
required |
version
|
str
|
Application version string. |
required |
output_dir
|
Path
|
Directory where the setup.exe is produced. |
required |
installer_type
|
str
|
Installer backend to use (default: "innosetup"). |
'innosetup'
|
installer_config
|
InstallerConfig | None
|
Config forwarded to the installer adapter. |
None
|
company_name
|
str
|
Publisher name; feeds the AppId GUID derivation
(see |
''
|
icon
|
str
|
Path to the setup wizard icon. |
''
|
main_file
|
str
|
Entry-point script; disambiguates the main executable
when the bundle holds several and none matches |
''
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
The produced setup.exe path. |
Raises:
| Type | Description |
|---|---|
InstallerError
|
When installer packaging fails. |
generate_iss_script
staticmethod
¶
generate_iss_script(
config: CompilerConfig,
output_path: Path,
*,
force: bool,
) -> Path
Render a standalone, committable .iss script for config.
Unlike the ephemeral build path, the icon is emitted verbatim
(never absolutized): the standalone script is meant to be committed,
and an absolute machine-specific path would defeat that. A relative
icon triggers a warning, since ISCC resolves it against the .iss
file's own directory rather than the process cwd.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Compiler configuration (provides installer, company_name, icon and project_name). |
required |
output_path
|
Path
|
Destination |
required |
force
|
bool
|
When False, refuse to overwrite an existing file. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
|
Raises:
| Type | Description |
|---|---|
InstallerConfigError
|
If |
ReleaseService¶
Service orchestrating secure-release packaging (TUF) and, optionally, publication.
ReleaseService
¶
Service orchestrating secure-release packaging and publication.
release_and_publish
staticmethod
¶
release_and_publish(
bundle_dir: Path,
app_name: str,
version: str,
repo_dir: Path,
*,
release_type: str = "tufup",
publish: bool = False,
pull_before: bool = False,
upload_type: str | None = None,
destination: str | None = None,
releaser_config: dict[str, Any] | None = None,
upload_config: dict[str, Any] | None = None,
required: bool = False,
) -> Path
Build the local TUF repo, then optionally publish it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle_dir
|
Path
|
Directory containing the compiled application. |
required |
app_name
|
str
|
Application name (used by tufup to name bundles). |
required |
version
|
str
|
Application version string. |
required |
repo_dir
|
Path
|
Root directory for the local TUF repository tree. |
required |
release_type
|
str
|
Release backend to use (default: "tufup"). |
'tufup'
|
publish
|
bool
|
When True, transfer the repository/ tree via an uploader. |
False
|
pull_before
|
bool
|
When True, download the current remote tree into
|
False
|
upload_type
|
str | None
|
Upload backend ("disk" or "server"). Required when publish=True. |
None
|
destination
|
str | None
|
Upload destination path or URL. Required when publish=True. |
None
|
releaser_config
|
dict[str, Any] | None
|
Extra config forwarded to the releaser adapter. |
None
|
upload_config
|
dict[str, Any] | None
|
Extra config forwarded to the uploader adapter. |
None
|
required
|
bool
|
Mark the version as mandatory for clients. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
The local |
Raises:
| Type | Description |
|---|---|
ValueError
|
When publish=True but upload_type or destination is missing. |
ReleaseError
|
When the version is not above a withdrawn version, or when release packaging or publishing fails. |
init_release
staticmethod
¶
init_release(
app_name: str,
repo_dir: Path,
keys_dir: Path,
*,
release_type: str = "tufup",
releaser_config: dict[str, Any] | None = None,
) -> bool
Create the releaser through the factory and delegate to init_keys.
Returns True if the init ran, False if it was already present (skip).
refresh_expiration
staticmethod
¶
refresh_expiration(
app_name: str,
repo_dir: Path,
keys_dir: Path,
*,
roles: tuple[str, ...] = (
"targets",
"snapshot",
"timestamp",
),
days: int | None = None,
release_type: str = "tufup",
releaser_config: dict[str, Any] | None = None,
) -> Path
Re-sign the metadata to push back expiration without a new release.
Native tufup keep-alive for projects updated irregularly.
Returns the local TUF repo directory.
TufService¶
Service reading the local TUF tree without tufup (status, expirations), recording withdrawn versions in withdrawn.json, and enforcing the release version rule.
TufService
¶
Local TUF tree operations shared by the CLI and the release path.
withdrawn_versions
staticmethod
¶
Versions removed from the tree, in withdrawal order.
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If |
record_withdrawn
staticmethod
¶
Append version to withdrawn.json.
The file is written atomically (temporary file in the same directory,
then os.replace): an interrupted write never truncates it.
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If the existing file is malformed, or if the write fails — the message names the version to add by hand. |
ensure_releasable
staticmethod
¶
Refuse a version that is not above every withdrawn version.
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If |
remove_latest
staticmethod
¶
remove_latest(
config: CompilerConfig, *, release_type: str = "tufup"
) -> str
Remove the latest version from the local tree and record it.
The signed tree (metadata/, targets/ and withdrawn.json) is
copied to a timestamped backup directory first, and the withdrawal is
recorded before the irreversible mutation: a releaser failure
(missing key, read-only keystore, expired role) can therefore never
leave a half-mutated tree with the withdrawal unrecorded. On failure the
three are restored from the backup, whose path is named in the error.
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The removed version. |
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If |
ReleaseError / SigningKeyError
|
From the releaser; the tree and the register are rolled back to their pre-removal state. |
status
staticmethod
¶
status(config: CompilerConfig) -> TufStatus
Read the local tree: versions, flags, expirations, withdrawals.
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If the tree is not initialized or a metadata file cannot be read. |
read_tree_version
staticmethod
¶
read_tree_version(
config: CompilerConfig,
*,
allow_empty: Literal[False] = False,
) -> str
read_tree_version(
config: CompilerConfig, *, allow_empty: bool
) -> str | None
read_tree_version(
config: CompilerConfig, *, allow_empty: bool = False
) -> str | None
Return the highest version signed in the local TUF tree.
The recap of publish update names this version, not
config.version: a config bumped without a rebuild would otherwise
announce a version the tree does not carry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Current configuration (repo dir and project name). |
required |
allow_empty
|
bool
|
True after the only version was withdrawn: the
empty tree is publishable and |
False
|
Returns:
| Type | Description |
|---|---|
str | None
|
str | None: Highest version among the |
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If no signed |
latest_tree_version
staticmethod
¶
Highest <app>-<version> archive signed in repo_dir.
Returns:
| Type | Description |
|---|---|
str | None
|
str | None: The raw version spelling tufup used, or |
Raises:
| Type | Description |
|---|---|
ReleaseError
|
If |
needs_full_archive
staticmethod
¶
True for the first release after a withdrawal.
tufup builds the patch from the latest archive still in the tree. Clients on a withdrawn version above it hold another archive: the patch would fail its hash check, so the release ships without one.
same_version
staticmethod
¶
Compare two version strings by PEP 440 meaning, not spelling.
1.2.3-rc.1 and 1.2.3rc1 are the same version. Strings that
are not PEP 440 versions fall back to an exact comparison.