CI/CD Toolkit
Phoenix2 is not a single repository: it is a large collection of independent libraries, written in C++, Rust and Python. Without a shared strategy, every repository would end up with its own, slightly different, hand-written pipeline for testing, building documentation, and publishing packages.
To avoid this, Phoenix2 ships its continuous integration and delivery (CI/CD) logic as reusable GitLab CI/CD Components, published in three per-language catalogs:
Any Phoenix2 repository gets its whole pipeline by simply include-ing the workflow component that matches its language, instead of writing and maintaining its own .gitlab-ci.yml from scratch. Improving the pipeline for every repository at once is then just a matter of releasing a new version of the toolkit.
Commit convention
Phoenix2 follows the Conventional Commits specification for every commit message. This is what allows the release process (described below) to be fully automated: the type of a commit tells the tooling exactly what kind of change it introduces, and therefore how the version number should evolve.
A commit message is prefixed by a type, for example:
feat: add mock backend for Unix sockets
fix: correct off-by-one error in circular buffer wraparound
docs: explain the record/replay workflow
The most common types are:
| Type | Meaning | Effect on version |
|---|---|---|
fix: |
a bug fix | bumps the patch version (x.y.Z) |
feat: |
a new feature | bumps the minor version (x.Y.0) |
perf: |
a performance improvement | bumps the patch version (x.y.Z) |
docs: |
documentation only | no release triggered |
refactor: |
code change that neither fixes a bug nor adds a feature | no release triggered |
test: |
adding or fixing tests | no release triggered |
chore:, ci:, build: |
tooling, CI, or build system changes | no release triggered |
Any commit that introduces a breaking change is marked with a ! after the type (e.g. feat!:) or a BREAKING CHANGE: footer in the commit body. This always bumps the major version (X.0.0), regardless of the type used.
This convention is not just a style guideline: it is what the release automation actually parses to decide whether, and how, to publish a new version.
One pipeline shape, three languages
Every Phoenix2 repository, whatever its language, goes through the same overall pipeline shape:
flowchart LR
V["Set package version"] --> B["Build pixi package"]
B --> T["Run tests"]
T --> C["Compute coverage"]
C --> D["Generate documentation"]
D --> P["Publish package"]
P --> Pages["Publish docs & coverage<br/>(GitLab Pages)"]
The three language-specific workflow components (phoenix-workflow-cpp, phoenix-workflow-rust, phoenix-workflow-python) implement this same shape, with a few language-specific differences:
- C++: the package is built and tested through
pixi/CMake/CTest, coverage is produced in Cobertura, SonarQube and HTML formats, and the resulting package is uploaded to the Phoenixconda/pixichannel onprefix.dev. Cross-platform builds (linux-64,linux-aarch64, macOS) are supported through a build matrix. - Python: same overall shape, with an additional
rufflinting step, and support for rendering documentation code snippets before building the docs. The package is likewise published to the Phoenixconda/pixichannel. - Rust: same overall shape, plus an extra step to publish the crate directly to crates.io, in addition to the
conda/pixipackage.
In all three cases, the package that is actually built, tested and published is a pixi/conda package, which keeps installation and dependency management consistent across the whole ecosystem, regardless of the implementation language.
From a commit on main to a published release
Testing, coverage, and documentation run on every pipeline (branches and merge requests alike). Releasing, however, only happens on the default branch:
flowchart TD
M["Commit merged to main"] --> S["semantic-release analyzes<br/>commit history since last release"]
S --> Ver["Next version computed<br/>from commit types"]
Ver --> Bump["Version bumped in<br/>pixi.toml, Cargo.toml, pyproject.toml, ..."]
Bump --> Tag["Git tag created"]
Tag --> Notes["Release notes generated"]
Notes --> Pub["Package published<br/>(conda channel, crates.io for Rust)"]
When a commit lands on main, the semantic-release component looks at every commit since the last release and, based on the commit types described above, computes the next version number. It then:
- updates the version wherever it needs to appear (
pixi.toml,Cargo.toml/Cargo.lock,pyproject.toml,CMakeLists.txt,codemeta.json, depending on the language), - creates the corresponding git tag,
- generates human-readable release notes from the commit messages,
- publishes a GitLab release,
- triggers the publication of the newly versioned package.
If no commit since the last release warrants a new version (only docs:, chore:, test:, ...), no release is created: semantic-release simply does nothing.
Keeping dependencies up to date: Renovate
On top of testing and releasing, Phoenix2 also automates dependency maintenance. A Renovate bot runs on an hourly schedule across Phoenix2 repositories. It checks whether newer versions of a project's dependencies have been published, and automatically opens merge requests to bump them. This keeps every repository's dependencies current without requiring a developer to track new upstream releases manually, while still going through the normal review and CI process before being merged.
Why reusable CI/CD components?
- Consistency: every Phoenix2 repository, in any language, is tested, documented, and released the same way.
- Low maintenance: a fix or improvement to the pipeline is made once, in the toolkit, and adopted by every repository simply by bumping the component's version.
- Reliability: releases are never manual. If it doesn't follow the commit convention, it doesn't ship a version bump; if it does, the version, changelog, tag, and package are always generated the same way.