Skip to content

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 Phoenix conda/pixi channel on prefix.dev. Cross-platform builds (linux-64, linux-aarch64, macOS) are supported through a build matrix.
  • Python: same overall shape, with an additional ruff linting step, and support for rendering documentation code snippets before building the docs. The package is likewise published to the Phoenix conda/pixi channel.
  • Rust: same overall shape, plus an extra step to publish the crate directly to crates.io, in addition to the conda/pixi package.

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:

  1. updates the version wherever it needs to appear (pixi.toml, Cargo.toml/Cargo.lock, pyproject.toml, CMakeLists.txt, codemeta.json, depending on the language),
  2. creates the corresponding git tag,
  3. generates human-readable release notes from the commit messages,
  4. publishes a GitLab release,
  5. 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.