Packaging and Release
Relevant source files
The following files were used as context for generating this wiki page:
The packaging and release process for ansible-core is managed by a comprehensive Python script, packaging/release.py. This script automates the entire lifecycle of a release, from version bumping and changelog generation to building, testing, and publishing artifacts. The process is designed to be consistent and reproducible, relying on a command-line interface to guide the releaser through a series of steps.
The system is centered around a two-phase workflow: prepare and complete. The prepare phase handles version updates and changelog generation, culminating in a pull request. After this PR is merged, the complete phase takes over to build the artifacts, publish them to PyPI, and tag the release on GitHub. This structured approach ensures that all necessary checks and updates are performed before a release is finalized.
Core Release Script: packaging/release.py
The primary tool for managing releases is the packaging/release.py script. It provides a command-line interface with several commands to orchestrate the release process.
Sources: packaging/release.py
Release Workflow
The release process is split into two main commands, prepare and complete, which should be executed in sequence with a manual PR merge step in between.
This diagram shows the high-level, two-phase release workflow.
Sources: packaging/release.py:1169-1184
Key Commands
The script exposes several commands to manage the release. The main entry points are prepare and complete, which orchestrate calls to other sub-commands.
| Command | Description |
|---|---|
instructions | Shows the step-by-step instructions for the release process. |
prepare | Prepares a release by updating the version, generating a changelog, and creating a release PR. |
complete | Completes a release by building, testing, publishing, and tagging. It also prepares a post-release PR. |
show-version | Displays the current and next calculated ansible-core version. |
check-state | Verifies that the git repository is in a clean and usable state. |
build | Builds the sdist and wheel distribution artifacts. |
test | Tests the built sdist and wheel artifacts. |
publish | Publishes the artifacts to a PyPI repository using twine. |
tag_release | Creates a tag and opens a GitHub release page with pre-filled release notes. |
Sources: packaging/release.py:1163-1383
prepare Command Flow
The prepare command initiates the release process. It handles all pre-build tasks and creates a pull request for review.
This sequence diagram illustrates the steps executed by the prepare command.
complete Command Flow
After the prepare pull request is merged, the complete command finalizes the release.
This sequence diagram illustrates the steps executed by the complete command.
Project and Build Configuration
The pyproject.toml file is central to the packaging process, defining the build environment, project metadata, and package contents.
Sources: pyproject.toml
Build System
The project uses setuptools as its build backend. The packaging/release.py script manages the version constraints for setuptools.
# pyproject.toml
[build-system]
requires = ["setuptools >= 77.0.3, <= 80.3.1"]
build-backend = "setuptools.build_meta"
Sources: pyproject.toml:2-4, packaging/release.py:1016-1043
Project Metadata
The [project] table in pyproject.toml defines standard package metadata such as the name, description, license, and classifiers. The version and dependencies are declared as dynamic and are sourced from ansible/release.py and requirements.txt respectively during the build process.
# pyproject.toml
[project]
requires-python = ">=3.12"
name = "ansible-core"
description = "Radically simple IT automation"
readme = "README.md"
license = "GPL-3.0-or-later"
dynamic = ["version", "dependencies"]
[tool.setuptools.dynamic]
version = {attr = "ansible.release.__version__"}
dependencies = {file = "requirements.txt"}
Sources: pyproject.toml:7-16, pyproject.toml:40-42
Console Scripts
The [project.scripts] table defines the command-line entry points that are created upon installation, linking commands like ansible-playbook to their corresponding main functions.
# pyproject.toml
[project.scripts]
ansible = "ansible.cli.adhoc:main"
ansible-config = "ansible.cli.config:main"
ansible-console = "ansible.cli.console:main"
ansible-doc = "ansible.cli.doc:main"
ansible-galaxy = "ansible.cli.galaxy:main"
ansible-playbook = "ansible.cli.playbook:main"
ansible-pull = "ansible.cli.pull:main"
ansible-vault = "ansible.cli.vault:main"
ansible-test = "ansible_test._util.target.cli.ansible_test_cli_stub:main"
Sources: pyproject.toml:61-71
Version Management
Versioning is strictly controlled by the release script. The canonical version string is stored in lib/ansible/release.py and is manipulated by the release script.
Version String
The version is defined by the __version__ variable. The release script uses a regex pattern to find and replace this value.
- File:
lib/ansible/release.py - Pattern:
^__version__ = '(?P<version>.*)'$ - Format:
__version__ = '{version}'
Sources: packaging/release.py:438-439, packaging/release.py:1000-1005
Version Modes
The VersionMode enum in packaging/release.py defines strict rules for version strings at different stages of the release process. This ensures that versions are correctly formatted (e.g., no epoch, no local component) and adhere to the expected state (e.g., dev, post-release, or final).
| Mode | Description |
|---|---|
DEFAULT | Disallows development and post-release versions. |
STRIP_POST | Strips the .postN suffix if present. |
REQUIRE_POST | Requires a post-release version (e.g., 2.15.0.post0). |
REQUIRE_DEV_POST | Requires either a development or a post-release version. |
ALLOW_DEV_POST | Allows any valid version, including development and post-release. |
Sources: packaging/release.py:451-481
Artifact Generation and Validation
Reproducible Builds
The build command is responsible for creating a source distribution (sdist) and a wheel. It ensures builds are reproducible by:
- Using
SOURCE_DATE_EPOCHenvironment variable, set to the commit timestamp. - Executing the build in a clean git worktree.
- Calling
create_reproducible_sdistto normalize metadata (timestamps, permissions) within the tarball.
Sources: packaging/release.py:1290-1322
Artifact Testing
The test command and its subcommands (test_sdist, test_wheel) validate the integrity and functionality of the built artifacts. The process involves:
- Creating a temporary virtual environment.
- Installing the artifact (
.tar.gzor.whl) into the venv usingpip. - Running basic commands like
ansible --versionandansible-test --versionto ensure the installation was successful. - For the sdist, it also checks for the absence of
.pycfiles.
Sources: packaging/release.py:1102-1120, packaging/release.py:1325-1350
Payload Packaging for Testing
Separate from the PyPI release packaging, the project uses a payload creation mechanism for running tests in isolated environments. The create_payload function in test/lib/ansible_test/_internal/payload.py creates a gzipped tarball (.tar.gz) of the Ansible source code.
This payload is used to delegate tests to virtual machines or containers. It carefully selects files, excludes vendored dependencies, and normalizes file permissions to ensure a consistent testing environment.