Releasing Eigen

Checklist for cutting an Eigen release (major / minor / patch) from the upstream repository at https://gitlab.com/libeigen/eigen.

The mechanical steps below are the what. The decisions around them (timing, scope, what counts as a breaking change) are a maintainer call and intentionally not encoded here.

Versioning

Eigen follows Semantic Versioning 2.0.0 as of 5.0 — see the transition table in CHANGELOG.md under the 5.0.0 entry. Versions are MAJOR.MINOR.PATCH:

  • Bump MAJOR for backward-incompatible API or ABI changes.
  • Bump MINOR for backward-compatible feature additions.
  • Bump PATCH for backward-compatible bug fixes only.

The legacy WORLD field is frozen at 3 for posterity and plays no role in release decisions; Eigen/Version keeps the #define EIGEN_WORLD_VERSION 3 line, but only MAJOR/MINOR/PATCH move.

Scope

Two flows are described:

  • Major / minor release (X.Y.0) — cuts a new X.Y release branch from master, ships the accumulated [Unreleased] work. Tag 5.0.0 is the most recent example.
  • Patch release (X.Y.Z, Z ≥ 1) — adds cherry-picked fixes to an existing X.Y release branch. Tag 5.0.1 is the most recent example.

Choosing between them is a judgment call (SemVer is the rule, but “large enough” varies). See work item #3051 for a recent case where a proposed 5.0.2 patch was reconsidered as a minor release once maintainers reviewed scope. Resolve this on the mailing list / Discord before any branch or version work.

Prerequisites

  • Push rights to upstream (https://gitlab.com/libeigen/eigen.git).
  • A GitLab personal access token with api scope, exported as GITLAB_PRIVATE_TOKEN. All scripts/gitlab_api_*.py helpers fall back to this environment variable.
  • A clean working tree on master (for a major / minor cut) or on the release branch (for a patch).

Version source of truth

Everything reads from Eigen/Version. Macros.h does not hard-code version numbers; CMakeLists.txt parses the #define lines from Eigen/Version at configure time. Editing Eigen/Version is the single version bump.

Field conventions observed in history:

StatePATCHPRERELEASEBUILDVERSION_STRING
At the release tagZ"""""X.Y.Z"
Release branch after the tag (dev)Z+1"dev""X.Y""X.Y.(Z+1)-dev+X.Y"
master between releases (dev)Z"dev""master""X.Y.Z-dev+master"

Reference commits:

  • 151b95d07bump to 5.0.0 (set the released form).
  • 0db477863Set 5.0.1 release version (same pattern, for a patch).
  • 4abf3bd54 / ccde35bcd — post-release dev bump on the release branch and on master respectively.

1. Pre-release (shared)

Gather the changelog material and label the included MRs / issues so the release::X.Y.Z query links in CHANGELOG.md resolve.

export GITLAB_PRIVATE_TOKEN=...

# 1. Dump everything that closed / was merged since the last release
#    (parallel). The scripts filter by `updated_at` (closest available
#    proxy for merge/close time), which can over-include — the human
#    narrows down in step 3.
python3 scripts/gitlab_api_mrs.py \
  --state merged \
  --updated_after  YYYY-MM-DD \
  --updated_before YYYY-MM-DD \
  --related_issues --closes_issues > mrs.json &
python3 scripts/gitlab_api_issues.py \
  --state closed \
  --updated_after  YYYY-MM-DD \
  --updated_before YYYY-MM-DD > issues.json &
wait

# 2. Map commits to their MRs / issues.
git log --pretty=%H <prev-tag>..<head> > commits.txt
python3 scripts/git_commit_mrs_and_issues.py \
  --merge_requests_file mrs.json \
  --commits commits.txt > commit_map.json

# 3. Decide the final included set and write it to filtered files
#    (selected_mrs.json / selected_issues.json) — typically by walking
#    `commit_map.json` and dropping anything out of scope. Then label
#    only that set; the CHANGELOG label-query links in sections 2c / 3c
#    depend on these labels.
python3 scripts/gitlab_api_labeller.py release::X.Y.Z \
  --mrs    $(jq -r '.[].iid' selected_mrs.json) \
  --issues $(jq -r '.[].iid' selected_issues.json)

2. Major / minor release (X.Y.0)

All steps are on the upstream repo. The release branch is just X.Y (no release/ prefix; matches existing 3.4, 5.0).

a. Cut the release branch from master.

git fetch upstream
git checkout -b X.Y upstream/master
git push upstream X.Y

b. On the release branch, set the released form of Eigen/Version.

#define EIGEN_MAJOR_VERSION X
#define EIGEN_MINOR_VERSION Y
#define EIGEN_PATCH_VERSION 0
#define EIGEN_PRERELEASE_VERSION ""
#define EIGEN_BUILD_VERSION ""
#define EIGEN_VERSION_STRING "X.Y.0"

c. Promote [Unreleased] in CHANGELOG.md to ## [X.Y.0] - YYYY-MM-DD. Use the 5.0.0 entry as the template — typical sub-sections include ### Versioning, ### Breaking changes, then per-area sections (### Elementwise math functions, ### Dense matrix decompositions, etc.). Include label-query links of the form https://gitlab.com/libeigen/eigen/-/issues?state=all&label_name%5B%5D=release%3A%3AX.Y.0 and the analogous merge-requests query.

d. Commit and tag. Tags are lightweight (no v prefix).

git commit Eigen/Version CHANGELOG.md -m "Set X.Y.0 release version."
git tag X.Y.0
git push upstream X.Y X.Y.0

e. Post-tag dev bump of the release branch. Set PATCH=1, PRERELEASE="dev", BUILD="X.Y", VERSION_STRING="X.Y.1-dev+X.Y". Commit subject: Update dev version number.

f. Post-tag bookkeeping on master. There is no rigid convention for how master's Eigen/Version advances after a release — today master tracks the next patch (5.0.1-dev+master); for a minor or major release decide with maintainers whether to bump MAJOR/MINOR on master as well.

3. Patch release (X.Y.Z, Z ≥ 1)

a. Cherry-pick fixes from master to the X.Y release branch.

git checkout X.Y
git pull upstream X.Y
git cherry-pick -x <sha>           # -x records the source SHA

Use cherry-pick -x consistently — git_commit_mrs_and_issues.py walks (cherry picked from commit ...) trailers to attribute work back to its original MR. Re-run CI on the branch after each pick (or after each batch) so a bad pick can be reverted in isolation.

Driving this by hand is tedious. Steve Bronder's apply_patches.py (linked from #3051) is prior art: it reads a CSV of SHAs, cherry-picks each onto a fresh branch, and runs configure / build / tests after each pick. The repo does not yet vendor an equivalent; if you write one, put it under scripts/.

b. On the release branch, set the released form of Eigen/Version. Bump PATCH to Z, clear PRERELEASE and BUILD, update VERSION_STRING to "X.Y.Z" (model: 0db477863).

c. Add a ## [X.Y.Z] - YYYY-MM-DD section to CHANGELOG.md. The 5.0.1 entry is the template: a short intro plus a bulleted list of fixes with [#nnnn] / [!nnnn] references, then the two release::X.Y.Z label-query links.

d. Commit and tag.

git commit Eigen/Version CHANGELOG.md -m "Set X.Y.Z release version."
git tag X.Y.Z
git push upstream X.Y X.Y.Z

e. Post-tag dev bump of the release branch. Set PATCH=Z+1, PRERELEASE="dev", BUILD="X.Y", VERSION_STRING="X.Y.(Z+1)-dev+X.Y". Commit subject: Update dev version number.

4. Publish archives to the GitLab package registry

After the tag is pushed, mirror GitLab‘s auto-generated tag archives into the project’s generic package registry with SHA-256 checksums:

python3 scripts/gitlab_api_deploy_package.py --version X.Y.Z

The script downloads eigen-X.Y.Z.{tar.gz,tar.bz2,tar,zip} from https://gitlab.com/libeigen/eigen/-/archive/X.Y.Z/, computes SHA-256 sums, and uploads each archive + its .sha256 companion to projects/15462818/packages/generic/eigen/X.Y.Z/.

5. Create the GitLab Release object

Manual step in the GitLab UI (no CI automation today): Project → Deploy → Releases → New release. Pick the tag, write a short description (link to the matching CHANGELOG.md section), and add the package-registry archive URLs from step 4 as release assets.

6. Documentation

Per-branch Doxygen output lands on GitLab Pages under https://libeigen.gitlab.io/eigen/docs-<branch> via the deploy:docs job in ci/deploy.gitlab-ci.yml. The job fires on schedule, on web-triggered pipelines, and on push to the default branch, only inside the libeigen namespace.

To publish docs for a release branch / tag, trigger a Web pipeline on the release branch from Build → Pipelines → Run pipeline. The PAGES_PREFIX becomes docs-<branch> and the URL becomes https://libeigen.gitlab.io/eigen/docs-<branch>.

Manual build fallback (developer machine, when CI Pages isn't an option):

mkdir -p build && cd build
cmake .. && make doc
# output: build/doc/html/

The scripts/eigen_gen_docs shell script is obsolete (it rsyncs to ssh.tuxfamily.org, the pre-GitLab docs host). Do not use it.

7. Announcements

All manual; no tooling in-repo.

  • Eigen mailing list.
  • Discord #announcements.
  • Project website news / wiki page (verify still maintained before posting).
  • Downstream packagers as best-effort: Homebrew, major Linux distros, Compiler Explorer (godbolt).

8. Cleanup

  • Open the release::X.Y.Z issue and MR query links from the new CHANGELOG.md entry and confirm they return non-empty results.
  • Close the GitLab milestone for this release if one was used.
  • On master, ensure CHANGELOG.md‘s [Unreleased] section is empty (or re-create it) so the next release’s notes start clean.