From cea2894ae69066afb66f85014269ca3f04b6fc29 Mon Sep 17 00:00:00 2001 From: Tiago Quintino Date: Thu, 16 Jul 2026 21:22:42 +0100 Subject: [PATCH 1/7] docs(languages): add the ECMWF Versioning (SemVer) policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Versioning.md placeholder with the authoritative Semantic Versioning policy: tag format (clean x.y.z, no 'v' prefix), MAJOR/MINOR/PATCH bump rules, 0.y.z initial development, deprecation/compatibility, production vs prerelease (x.y.z-upstream.N) tags, single-source-of-truth (git tag), and — the previously missing piece — how to version multi-language / multi-package repositories under one version number (all artefacts released together; independent version lines imply separate repositories; compiled-wheel x.y.z.C build identifier). Cross-reference it from the documents that touch versioning: - Languages/README (index description), Guidelines/Branching, Guidelines/ External-Contributions (Tagging Rules), Languages/Python-Wheels, the open-source-audit skill's version-tag check, and the SMP release-strategy line. - Remove the resolved Versioning gap from TODO.md. --- Agent Skills/open-source-audit/SKILL.md | 2 +- Guidelines/Branching.md | 2 +- Guidelines/External-Contributions.md | 2 + Languages/Python-Wheels.md | 2 +- Languages/README.md | 2 +- Languages/Versioning.md | 117 +++++++++++++++++++++++- Software Management Plan/README.md | 2 +- TODO.md | 1 - 8 files changed, 122 insertions(+), 8 deletions(-) diff --git a/Agent Skills/open-source-audit/SKILL.md b/Agent Skills/open-source-audit/SKILL.md index 756ca01..340c097 100644 --- a/Agent Skills/open-source-audit/SKILL.md +++ b/Agent Skills/open-source-audit/SKILL.md @@ -379,7 +379,7 @@ publication. Removing the file in a new commit is not sufficient. best proxy for "publishable". - [ ] Version tags follow SemVer if the project has releases: ECMWF production tags use the clean `x.y.z` form (no `v` prefix) - [Codex: Guidelines/External-Contributions.md]. + [Codex: Languages/Versioning.md; Guidelines/External-Contributions.md]. - [ ] A `CHANGELOG.md` is not required — but if one exists, check it is sane: entries match actual tags, no placeholder sections, no internal references. diff --git a/Guidelines/Branching.md b/Guidelines/Branching.md index 6891dc0..59cc36a 100644 --- a/Guidelines/Branching.md +++ b/Guidelines/Branching.md @@ -1,6 +1,6 @@ # Branching Patterns -This document describes the branching patterns used across ECMWF repositories. The choice of pattern for a given repository is governed by [ADR-001: Git Branching Model](../ADR/ADR-001-Git-Branching-Model.md). +This document describes the branching patterns used across ECMWF repositories. The choice of pattern for a given repository is governed by [ADR-001: Git Branching Model](../ADR/ADR-001-Git-Branching-Model.md). Release version numbers and tags follow the [Versioning](../Languages/Versioning.md) policy (Semantic Versioning, clean `x.y.z`, no `v` prefix). ## Contents diff --git a/Guidelines/External-Contributions.md b/Guidelines/External-Contributions.md index 8eb30e7..44eb712 100644 --- a/Guidelines/External-Contributions.md +++ b/Guidelines/External-Contributions.md @@ -89,6 +89,8 @@ Contractors may work in one of two ways: ### Tagging Rules +These tags follow ECMWF's [Versioning](../Languages/Versioning.md) policy (Semantic Versioning, clean `x.y.z`, no `v` prefix); the rules below cover the contractor prerelease and production tags specifically. + Contractors using this model may create prerelease tags for internal testing and staging: ``` diff --git a/Languages/Python-Wheels.md b/Languages/Python-Wheels.md index 1ae2482..68f55c9 100644 --- a/Languages/Python-Wheels.md +++ b/Languages/Python-Wheels.md @@ -43,7 +43,7 @@ Say we release multio with version A, depending on mir B and eckit C, with exact Later on, a new version C+1 of eckit appears, and then we'd like to release multio A+1. However, if there is no mir of version B+1, we are stuck -- the mirlib-B.wheel already exists, and it depends on eckit-A.wheel, not eckit-A+1.wheel. -To get around that limitation, we switch to 4-dimensional versioning, x.y.z.C, where C is a monotonic counter shared across all packages. +To get around that limitation, we extend ECMWF's base [Semantic Versioning](./Versioning.md) scheme to 4-dimensional versioning, x.y.z.C, where C is a monotonic counter shared across all packages (this is a wheel build identifier and does not change the software's `x.y.z` release version). For the example above, we would first release eckit.A.1, mir.B.1 and multio.C.1. And in the second run, we would release eckit.A+1.2, mir.B.2 and multio.C2, all with exact pins. The mir.B.1 and mir.B.2 are seemingly the same, from the point of view of the compiled mir code -- but they actually differ in which version of eckit they were build against, and thus justify being separate wheels. diff --git a/Languages/README.md b/Languages/README.md index 1e11f32..b3e4450 100644 --- a/Languages/README.md +++ b/Languages/README.md @@ -6,4 +6,4 @@ Language-specific guidelines and tooling for ECMWF software projects. - [C++](./C++/README.md) — coding standards, clang-format and clang-tidy configuration - [Python Wheels](./Python-Wheels.md) — building and using Python wheels with compiled libraries -- [Versioning](./Versioning.md) — versioning guidance for multi-language repositories +- [Versioning](./Versioning.md) — ECMWF's Semantic Versioning policy: tag format (`x.y.z`, no `v` prefix), MAJOR/MINOR/PATCH bump rules, production vs prerelease tags, and single-version guidance for multi-language / multi-package repositories diff --git a/Languages/Versioning.md b/Languages/Versioning.md index 6561b87..033c770 100644 --- a/Languages/Versioning.md +++ b/Languages/Versioning.md @@ -1,3 +1,116 @@ -# TODO +# Versioning -How do we manage multi-language repositories with a single version number? \ No newline at end of file +ECMWF software is versioned with **Semantic Versioning (SemVer)**. This document +is the authoritative policy for how to version an ECMWF repository — the tag +format, when to bump each component, prerelease and production tags, and how to +version a repository that contains multiple languages or packages with a single +version number. + +It consolidates the version-tag rules used in +[Branching](../Guidelines/Branching.md) and +[External Contributions](../Guidelines/External-Contributions.md); those +documents govern *how* releases are branched and delivered, while this document +defines *how the version number itself is chosen and applied*. + +## Semantic Versioning + +Versions have the form: + +``` +MAJOR.MINOR.PATCH e.g. 2.4.1 +``` + +Given a public interface (a library API, a CLI, a data/format contract, a wire +protocol), increment the: + +- **MAJOR** version for **incompatible / breaking changes** — anything that + requires downstream users to change their code, commands, configuration, or + data handling. +- **MINOR** version for **backwards-compatible new functionality** (and for + marking features as deprecated). +- **PATCH** version for **backwards-compatible bug fixes** only. + +See [semver.org](https://semver.org) for the full specification. + +### No `v` prefix + +ECMWF version tags use the **clean `x.y.z` form with no `v` prefix** (`2.4.1`, +not `v2.4.1`). This is checked by the `open-source-audit` skill. Note that this +applies to *ECMWF release tags*; third-party GitHub Actions and other external +artefacts keep their own tag conventions (e.g. `actions/checkout@v4`). + +### Initial development (`0.y.z`) + +A project below `1.0.0` is in initial development: **anything may change at any +time** and the public interface is not considered stable. Reach `1.0.0` when the +interface is stable and the software is used in anger. New public repositories +usually start at a low [Project Maturity](../Project%20Maturity/README.md) level, +which is a better signal of stability for users than the version number alone. + +### Deprecation and compatibility + +- Prefer to **deprecate before removing**: introduce the replacement, mark the + old interface deprecated (a MINOR release), keep it working for at least one + MINOR cycle, then remove it in a MAJOR release. +- Document breaking changes prominently (release notes / `CHANGELOG`), and a + `CHANGELOG` is recommended so users can see what changed between versions. + +## Production and prerelease tags + +- **Production tags** are clean `x.y.z` tags on `main`/`master`. Only such tags + represent production-ready software. +- **Prerelease tags** use the form `x.y.z-upstream.N` (`N` = a sequential + prerelease number), for contractor-side testing, staging, and CI. They are + **non-production**, must not appear on `main`/`master`, and must never be + deployed beyond development or test environments. + +The full rules, and the delivery/acceptance workflow they belong to, are in +[External Contributions → Tagging Rules](../Guidelines/External-Contributions.md). +The mechanics of creating and pushing release tags are in +[Branching](../Guidelines/Branching.md). + +## Single source of truth + +A repository has **one** version number. Do not maintain the version in several +places that can drift. The **git tag is authoritative**; build tooling should +derive the package/library version from it rather than hard-coding it — for +example `setuptools_scm` for Python, or a single top-level `VERSION` file (or the +`project.version` in `pyproject.toml` / the CMake `project(... VERSION ...)`) +that the build reads. A generated version file (e.g. `_version.py`) is a build +artefact and must not be committed (see [Repository Structure](../Repository%20Structure/README.md)). + +## Multi-language and multi-package repositories + +A repository that contains more than one language or produces more than one +artefact (for example a C/C++ library with Python bindings, or a package plus +its CLI) is versioned as **a single unit under one version number**: + +- **One version for the whole repository.** All components and artefacts built + from a commit share the same `x.y.z`, are tagged once, and are released + together. Do not version the Python bindings independently of the C++ library + they wrap within the same repository. +- **Bump for the repository as a whole.** A breaking change in *any* public + component is a MAJOR bump for the repository; new backwards-compatible + functionality in any component is a MINOR bump; and so on. This keeps a single, + unambiguous compatibility signal for downstream users. +- **If components genuinely need to evolve on independent version lines**, that + is a signal they should live in **separate repositories**, each with its own + single version, rather than being force-fitted into one. + +### Compiled Python wheels + +Where a repository publishes compiled Python wheels split across interdependent +packages (e.g. a library wheel and its Python interface wheel), the base `x.y.z` +scheme is extended to a **four-component `x.y.z.C`** form, where `C` is a +monotonic counter shared across the packages so that matching builds can be +pinned together. This mechanism, and its current limitations, are described in +[Python Wheels](./Python-Wheels.md); it does not change the `x.y.z` release +version of the software, only the wheel build identifier. + +## Summary + +- Use SemVer `MAJOR.MINOR.PATCH` (`x.y.z`), **no `v` prefix**. +- MAJOR = breaking, MINOR = backwards-compatible feature, PATCH = backwards-compatible fix; `0.y.z` = unstable. +- Production tags are clean `x.y.z` on `main`/`master`; prereleases are `x.y.z-upstream.N` (non-production). +- One version per repository; the git tag is the single source of truth; all languages/artefacts release together. +- Compiled Python wheels may use `x.y.z.C` for build pinning (see [Python Wheels](./Python-Wheels.md)). diff --git a/Software Management Plan/README.md b/Software Management Plan/README.md index b865e95..ff93404 100644 --- a/Software Management Plan/README.md +++ b/Software Management Plan/README.md @@ -115,7 +115,7 @@ Include: - Branching model - Merge criteria, e.g. pull requests with mandatory X reviews, all CI/CD test pass - **Release strategy** - - Versioning system, e.g. *"Semantic versioning (MAJOR.MINOR.PATCH)"* + - Versioning system — ECMWF uses Semantic Versioning (MAJOR.MINOR.PATCH); see the [Versioning](../Languages/Versioning.md) policy - **Maintenance** - How will future developments, and user requests, be managed? - **Maturity timeline** diff --git a/TODO.md b/TODO.md index f67351f..5d01262 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ Contributions addressing any of these items are welcome via pull request. ## High Priority - [ ] **Guidelines/Testing.md** — File opens with an explicit `TODO`. No actual testing guidelines exist beyond test data hosting via `sites.ecmwf.int`. Needs unit, integration, and regression testing standards. -- [ ] **Languages/Versioning.md** — Entire file is a single-line placeholder: *"How do we manage multi-language repositories with a single version number?"*. Needs a versioning policy. - [ ] **LICENSE** (root) — File is empty. The licence text exists in `Legal/Apache-Licence` but the root `LICENSE` file that GitHub uses for badge detection and display contains no content. ## Medium Priority From 8098cbb5cce7669d85d7d770d1685ec731590e83 Mon Sep 17 00:00:00 2001 From: Tiago Quintino Date: Thu, 16 Jul 2026 21:27:22 +0100 Subject: [PATCH 2/7] docs: fix Python-Wheels example typos (multio.C.2; 'were built against') (Copilot) --- Languages/Python-Wheels.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Languages/Python-Wheels.md b/Languages/Python-Wheels.md index 68f55c9..44a683e 100644 --- a/Languages/Python-Wheels.md +++ b/Languages/Python-Wheels.md @@ -45,8 +45,8 @@ However, if there is no mir of version B+1, we are stuck -- the mirlib-B.wheel a To get around that limitation, we extend ECMWF's base [Semantic Versioning](./Versioning.md) scheme to 4-dimensional versioning, x.y.z.C, where C is a monotonic counter shared across all packages (this is a wheel build identifier and does not change the software's `x.y.z` release version). For the example above, we would first release eckit.A.1, mir.B.1 and multio.C.1. -And in the second run, we would release eckit.A+1.2, mir.B.2 and multio.C2, all with exact pins. -The mir.B.1 and mir.B.2 are seemingly the same, from the point of view of the compiled mir code -- but they actually differ in which version of eckit they were build against, and thus justify being separate wheels. +And in the second run, we would release eckit.A+1.2, mir.B.2 and multio.C.2, all with exact pins. +The mir.B.1 and mir.B.2 are seemingly the same, from the point of view of the compiled mir code -- but they actually differ in which version of eckit they were built against, and thus justify being separate wheels. This has an additional benefit of allowing simple check of ABI compatibility of a given python environment. List all installed wheels and check that their fourth versioning number is _exactly_ equal. From dcacf6233291aa2fa655c88df0e51e2f7c01cf6c Mon Sep 17 00:00:00 2001 From: Tiago Quintino Date: Thu, 16 Jul 2026 21:33:36 +0100 Subject: [PATCH 3/7] docs: clarify wheel build identifier vs SemVer; fix wheel example placeholders (Copilot) - Versioning.md: x.y.z.N is a packaging/build identifier for wheels, not a SemVer version; the release version and git tag remain x.y.z. - Python-Wheels: correct the A/C placeholder swap so the example matches its own definition (multio=A, mir=B, eckit=C); rename the 4th-component counter to N to avoid clashing with eckit's version letter C. --- Languages/Python-Wheels.md | 8 ++++---- Languages/Versioning.md | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Languages/Python-Wheels.md b/Languages/Python-Wheels.md index 44a683e..3813b89 100644 --- a/Languages/Python-Wheels.md +++ b/Languages/Python-Wheels.md @@ -41,11 +41,11 @@ We could thus switch to exact pinning, ie, say that the mirlib wheel in question However, that introduces a problem once we realize that our compiled stack consists of multiple libraries. Say we release multio with version A, depending on mir B and eckit C, with exact pins. Later on, a new version C+1 of eckit appears, and then we'd like to release multio A+1. -However, if there is no mir of version B+1, we are stuck -- the mirlib-B.wheel already exists, and it depends on eckit-A.wheel, not eckit-A+1.wheel. +However, if there is no mir of version B+1, we are stuck -- the mirlib-B.wheel already exists, and it depends on eckit-C.wheel, not eckit-C+1.wheel. -To get around that limitation, we extend ECMWF's base [Semantic Versioning](./Versioning.md) scheme to 4-dimensional versioning, x.y.z.C, where C is a monotonic counter shared across all packages (this is a wheel build identifier and does not change the software's `x.y.z` release version). -For the example above, we would first release eckit.A.1, mir.B.1 and multio.C.1. -And in the second run, we would release eckit.A+1.2, mir.B.2 and multio.C.2, all with exact pins. +To get around that limitation, we add a fourth component to the wheel version, x.y.z.N, where N is a monotonic counter shared across all packages (this is a wheel build identifier and does not change the software's `x.y.z` release version — see the [Versioning](./Versioning.md) policy). +For the example above, we would first release eckit.C.1, mir.B.1 and multio.A.1 (all with build counter `1`). +And in the second run, we would release eckit.C+1.2, mir.B.2 and multio.A.2 (build counter `2`), all with exact pins. The mir.B.1 and mir.B.2 are seemingly the same, from the point of view of the compiled mir code -- but they actually differ in which version of eckit they were built against, and thus justify being separate wheels. This has an additional benefit of allowing simple check of ABI compatibility of a given python environment. diff --git a/Languages/Versioning.md b/Languages/Versioning.md index 033c770..7f1ad6f 100644 --- a/Languages/Versioning.md +++ b/Languages/Versioning.md @@ -100,12 +100,13 @@ its CLI) is versioned as **a single unit under one version number**: ### Compiled Python wheels Where a repository publishes compiled Python wheels split across interdependent -packages (e.g. a library wheel and its Python interface wheel), the base `x.y.z` -scheme is extended to a **four-component `x.y.z.C`** form, where `C` is a -monotonic counter shared across the packages so that matching builds can be -pinned together. This mechanism, and its current limitations, are described in -[Python Wheels](./Python-Wheels.md); it does not change the `x.y.z` release -version of the software, only the wheel build identifier. +packages (e.g. a library wheel and its Python interface wheel), a +**four-component build identifier `x.y.z.N`** is used for the *wheels*, where `N` +is a monotonic counter shared across the packages so that ABI-compatible builds +can be pinned together. This is a **packaging/build identifier, not a SemVer +version**: the software's release version — and the authoritative git tag — +remains `x.y.z`. The mechanism, and its current limitations, are described in +[Python Wheels](./Python-Wheels.md). ## Summary @@ -113,4 +114,4 @@ version of the software, only the wheel build identifier. - MAJOR = breaking, MINOR = backwards-compatible feature, PATCH = backwards-compatible fix; `0.y.z` = unstable. - Production tags are clean `x.y.z` on `main`/`master`; prereleases are `x.y.z-upstream.N` (non-production). - One version per repository; the git tag is the single source of truth; all languages/artefacts release together. -- Compiled Python wheels may use `x.y.z.C` for build pinning (see [Python Wheels](./Python-Wheels.md)). +- Compiled Python wheels may use a `x.y.z.N` build identifier for pinning (see [Python Wheels](./Python-Wheels.md)). From 4da69d406e9970464df48747dbb66043697376d9 Mon Sep 17 00:00:00 2001 From: Tiago Quintino Date: Thu, 16 Jul 2026 21:37:41 +0100 Subject: [PATCH 4/7] docs: finish wheel example placeholder fix + 'duly'; 'used in production' (Copilot) - Python-Wheels: the later pip example now uses the consistent placeholders (multio.A.1/eckit.C.1/eckit.C+1.2); fix 'dully' -> 'duly'. - Versioning.md: 'used in production' instead of 'used in anger'. --- Languages/Python-Wheels.md | 6 +++--- Languages/Versioning.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Languages/Python-Wheels.md b/Languages/Python-Wheels.md index 3813b89..a1a76e7 100644 --- a/Languages/Python-Wheels.md +++ b/Languages/Python-Wheels.md @@ -52,10 +52,10 @@ This has an additional benefit of allowing simple check of ABI compatibility of List all installed wheels and check that their fourth versioning number is _exactly_ equal. If not, you are likely to experience "Symbol not found" or worse. The reason for this possibly happening is that pip does not necessarily guarantee to leave your environment in a correct state. -Say you first `pip install multio`, and pip notices that there is multio.C.1, which in turn brings eckit.A.1. +Say you first `pip install multio`, and pip notices that there is multio.A.1, which in turn brings eckit.C.1. And then later, you `pip install gribjump` -- which is not in any relationship to multio, but depends on eckit. -And say that the most recent gribjump wheel has been released as gribjump.D.2, with eckit.A+1.2 as a dependency. -Pip dully updates eckit, while telling you "oh and btw your environment is broken, multio wheel has unsatisfied dependency". +And say that the most recent gribjump wheel has been released as gribjump.D.2, with eckit.C+1.2 as a dependency. +Pip duly updates eckit, while telling you "oh and btw your environment is broken, multio wheel has unsatisfied dependency". Other package managers like `uv` would refuse to install multio in the first place, but this behaviour can't be relied upon. diff --git a/Languages/Versioning.md b/Languages/Versioning.md index 7f1ad6f..0a506c5 100644 --- a/Languages/Versioning.md +++ b/Languages/Versioning.md @@ -43,7 +43,7 @@ artefacts keep their own tag conventions (e.g. `actions/checkout@v4`). A project below `1.0.0` is in initial development: **anything may change at any time** and the public interface is not considered stable. Reach `1.0.0` when the -interface is stable and the software is used in anger. New public repositories +interface is stable and the software is used in production. New public repositories usually start at a low [Project Maturity](../Project%20Maturity/README.md) level, which is a better signal of stability for users than the version number alone. From 9e99e563c37c3b81db2f21d2a4d3c6930fb99948 Mon Sep 17 00:00:00 2001 From: Tiago Quintino Date: Thu, 16 Jul 2026 21:42:26 +0100 Subject: [PATCH 5/7] docs: clarify tag-as-source-of-truth (mirror rule); format x.y.z.N as code (Copilot) - Versioning.md: git tag is the single source of truth; prefer deriving the version automatically; any in-repo version field is a single mirror that must match the tag. - Python-Wheels: format the x.y.z.N / N literals as inline code. --- Languages/Python-Wheels.md | 2 +- Languages/Versioning.md | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Languages/Python-Wheels.md b/Languages/Python-Wheels.md index a1a76e7..1e384a7 100644 --- a/Languages/Python-Wheels.md +++ b/Languages/Python-Wheels.md @@ -43,7 +43,7 @@ Say we release multio with version A, depending on mir B and eckit C, with exact Later on, a new version C+1 of eckit appears, and then we'd like to release multio A+1. However, if there is no mir of version B+1, we are stuck -- the mirlib-B.wheel already exists, and it depends on eckit-C.wheel, not eckit-C+1.wheel. -To get around that limitation, we add a fourth component to the wheel version, x.y.z.N, where N is a monotonic counter shared across all packages (this is a wheel build identifier and does not change the software's `x.y.z` release version — see the [Versioning](./Versioning.md) policy). +To get around that limitation, we add a fourth component to the wheel version, `x.y.z.N`, where `N` is a monotonic counter shared across all packages (this is a wheel build identifier and does not change the software's `x.y.z` release version — see the [Versioning](./Versioning.md) policy). For the example above, we would first release eckit.C.1, mir.B.1 and multio.A.1 (all with build counter `1`). And in the second run, we would release eckit.C+1.2, mir.B.2 and multio.A.2 (build counter `2`), all with exact pins. The mir.B.1 and mir.B.2 are seemingly the same, from the point of view of the compiled mir code -- but they actually differ in which version of eckit they were built against, and thus justify being separate wheels. diff --git a/Languages/Versioning.md b/Languages/Versioning.md index 0a506c5..6b4d1e0 100644 --- a/Languages/Versioning.md +++ b/Languages/Versioning.md @@ -71,13 +71,16 @@ The mechanics of creating and pushing release tags are in ## Single source of truth -A repository has **one** version number. Do not maintain the version in several -places that can drift. The **git tag is authoritative**; build tooling should -derive the package/library version from it rather than hard-coding it — for -example `setuptools_scm` for Python, or a single top-level `VERSION` file (or the -`project.version` in `pyproject.toml` / the CMake `project(... VERSION ...)`) -that the build reads. A generated version file (e.g. `_version.py`) is a build -artefact and must not be committed (see [Repository Structure](../Repository%20Structure/README.md)). +A repository has **one** version number, and the **git tag is the single source +of truth**. Do not maintain the version in several places that can drift. +Prefer to **derive** the package/library version from the tag automatically +(e.g. `setuptools_scm` for Python, or CMake reading `git describe`) so there is +nothing to keep in sync. If an in-repo version field is unavoidable (for example +`project.version` in `pyproject.toml`, a CMake `project(... VERSION ...)`, or a +top-level `VERSION` file), keep **exactly one** such field and treat it as a +mirror that must always match the tag. A generated version file (e.g. +`_version.py`) is a build artefact and must not be committed (see +[Repository Structure](../Repository%20Structure/README.md)). ## Multi-language and multi-package repositories From 80bcdc9d2e3cb1dbcc668783f2cf339e2ff99c5c Mon Sep 17 00:00:00 2001 From: Tiago Quintino Date: Thu, 16 Jul 2026 21:45:43 +0100 Subject: [PATCH 6/7] docs: use eckitlib wheel name for -lib convention consistency (Copilot) --- Languages/Python-Wheels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Languages/Python-Wheels.md b/Languages/Python-Wheels.md index 1e384a7..3169f67 100644 --- a/Languages/Python-Wheels.md +++ b/Languages/Python-Wheels.md @@ -41,7 +41,7 @@ We could thus switch to exact pinning, ie, say that the mirlib wheel in question However, that introduces a problem once we realize that our compiled stack consists of multiple libraries. Say we release multio with version A, depending on mir B and eckit C, with exact pins. Later on, a new version C+1 of eckit appears, and then we'd like to release multio A+1. -However, if there is no mir of version B+1, we are stuck -- the mirlib-B.wheel already exists, and it depends on eckit-C.wheel, not eckit-C+1.wheel. +However, if there is no mir of version B+1, we are stuck -- the mirlib-B.wheel already exists, and it depends on eckitlib-C.wheel, not eckitlib-C+1.wheel. To get around that limitation, we add a fourth component to the wheel version, `x.y.z.N`, where `N` is a monotonic counter shared across all packages (this is a wheel build identifier and does not change the software's `x.y.z` release version — see the [Versioning](./Versioning.md) policy). For the example above, we would first release eckit.C.1, mir.B.1 and multio.A.1 (all with build counter `1`). From c1695ad1ad0a92c96f829e36a142c2470c2eba97 Mon Sep 17 00:00:00 2001 From: Tiago Quintino Date: Thu, 16 Jul 2026 21:49:43 +0100 Subject: [PATCH 7/7] docs: format wheel/version identifiers in the Python-Wheels example as inline code (Copilot) --- Languages/Python-Wheels.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Languages/Python-Wheels.md b/Languages/Python-Wheels.md index 3169f67..d0c8267 100644 --- a/Languages/Python-Wheels.md +++ b/Languages/Python-Wheels.md @@ -41,20 +41,20 @@ We could thus switch to exact pinning, ie, say that the mirlib wheel in question However, that introduces a problem once we realize that our compiled stack consists of multiple libraries. Say we release multio with version A, depending on mir B and eckit C, with exact pins. Later on, a new version C+1 of eckit appears, and then we'd like to release multio A+1. -However, if there is no mir of version B+1, we are stuck -- the mirlib-B.wheel already exists, and it depends on eckitlib-C.wheel, not eckitlib-C+1.wheel. +However, if there is no mir of version B+1, we are stuck -- the `mirlib-B.wheel` already exists, and it depends on `eckitlib-C.wheel`, not `eckitlib-C+1.wheel`. To get around that limitation, we add a fourth component to the wheel version, `x.y.z.N`, where `N` is a monotonic counter shared across all packages (this is a wheel build identifier and does not change the software's `x.y.z` release version — see the [Versioning](./Versioning.md) policy). -For the example above, we would first release eckit.C.1, mir.B.1 and multio.A.1 (all with build counter `1`). -And in the second run, we would release eckit.C+1.2, mir.B.2 and multio.A.2 (build counter `2`), all with exact pins. -The mir.B.1 and mir.B.2 are seemingly the same, from the point of view of the compiled mir code -- but they actually differ in which version of eckit they were built against, and thus justify being separate wheels. +For the example above, we would first release `eckit.C.1`, `mir.B.1` and `multio.A.1` (all with build counter `1`). +And in the second run, we would release `eckit.C+1.2`, `mir.B.2` and `multio.A.2` (build counter `2`), all with exact pins. +The `mir.B.1` and `mir.B.2` are seemingly the same, from the point of view of the compiled mir code -- but they actually differ in which version of eckit they were built against, and thus justify being separate wheels. This has an additional benefit of allowing simple check of ABI compatibility of a given python environment. List all installed wheels and check that their fourth versioning number is _exactly_ equal. If not, you are likely to experience "Symbol not found" or worse. The reason for this possibly happening is that pip does not necessarily guarantee to leave your environment in a correct state. -Say you first `pip install multio`, and pip notices that there is multio.A.1, which in turn brings eckit.C.1. +Say you first `pip install multio`, and pip notices that there is `multio.A.1`, which in turn brings `eckit.C.1`. And then later, you `pip install gribjump` -- which is not in any relationship to multio, but depends on eckit. -And say that the most recent gribjump wheel has been released as gribjump.D.2, with eckit.C+1.2 as a dependency. +And say that the most recent gribjump wheel has been released as `gribjump.D.2`, with `eckit.C+1.2` as a dependency. Pip duly updates eckit, while telling you "oh and btw your environment is broken, multio wheel has unsatisfied dependency". Other package managers like `uv` would refuse to install multio in the first place, but this behaviour can't be relied upon.