PSA: On version schemes for GNOME components

There is a certain level of misunderstanding between GNOME maintainers as to how to adapt to new versioning schemes, in light of the versioning scheme introduced two years ago with GNOME 40.

I am going to try and address the misunderstanding and offer a few options for maintainers; if you are a maintainer of a GNOME module, you’re kindly encouraged to follow the best practices, instead of making your own, in the interest of consistency and predictability.

Remember: version tuples don’t mean anything by themselves. You have to decide what kind of ordering and meaning you impose on them. The recommendations in this topic are, of course, advisory, but they should be taken in the same stride as the keywords in RFC 2119: conformance is beneficial for all parties involved, and any deviation should be weighted against all possible downsides.

Rule 0: versions are not numbers

The fact that version tuples can be composed by numbers does not imply that versions are, or behave like natural numbers. Don’t treat them as numbers, and don’t assume that just because some of their components look like numbers they have the same properties as numbers.

Version numbers alone don’t tell you that a release is newer or older than another; version numbers alone don’t tell you that a release is stable or unstable; version numbers alone don’t tell you if it’s safe to upgrade from one release to another.

Do not make the mistake of thinking that version numbers are, by themselves, self-explanatory.

If you follow the tenets of Rule 0 you’ll avoid leading yourself, the people packaging your project, and the people using your project, into a deep, dark, rabbit hole of pain.

Versions

Let us assume we have a library, called libexample; this library follows the GNOME development cycle:

  • six months development followed by an arbitrary number of stable releases
  • the API is grouped under a single namespace
  • the API’s namespace has a “major” version
  • stable releases can only fix issues without introducing new API
  • new symbols can only be added during a development cycle
  • old symbols can only be deprecated during a development cycle
  • no symbol can be removed or changed within the same API version

See also: API stability.

Given the nature of the cycle, a version identifier will typically have three components:

  1. a “major” component, indicating the API version
  2. a “minor” component, indicating the current cycle within the same API version
  3. a “micro”, or “patch” component, indicating the release within the same cycle

The maintainer of libexample now has the option of choosing a versioning scheme, roughly based on the “semantic versioning” rules:

Option 1a: Classic even/odd versioning scheme

Releases made during the development cycle have an odd number for the “minor” component; releases made during a stable cycle have an even number for the “minor” component.

Examples

  • 1.1.2: development release
  • 1.2.0: stable release
  • 1.3.3: development release
  • 1.2.5: stable release

Option 1b: Cairo-style even/odd versioning scheme

An elegant versioning scheme, for a more… civilized age.

Works just like the even/odd scheme of Option 1a, with these additional rules

  • the “micro” component in source revision is always odd
  • the “micro” component for releases is always even
  • a value of “0” for the “micro” component is always reserved for the first stable release

Example

  • 1.1.0: does not exist
  • 1.1.1: development version in the code repository
  • 1.2.0: first release of the 1.2 stable cycle
  • 1.2.1: stable version in the code repository
  • 1.3.2: first development snapshot of the 1.3 development cycle

Option 2: Monotonic version with initial target update

The minor version number is increased at the beginning of the development cycle.

Development snapshots use a different identifier in the “micro” component.

Examples

  • 1.1.0: first stable release of the 1.1 cycle
  • 1.2.alpha: first developers snapshot of the 1.2 cycle
  • 1.2.beta: second developers snapshot of the 1.2 cycle
  • 1.2.rc: release candidate of the 1.2 cycle
  • 1.2.0: first stable release of the 1.2 cycle
  • 1.3.alpha: first developers snapshot of the 1.3 cycle
  • 1.3.0.rc: does not exist

Note: The alpha/beta/rc identifiers are the micro component. DO NOT use them as suffixes.

Option 3: Monotonic version with final target update

The minor version number is increased at the end of the development cycle.

Development snapshot use a different identifier in the “micro” component.

Examples

  • 1.1.0: first stable release of the 1.1 cycle
  • 1.1.90: first developers snapshot of the 1.2 cycle
  • 1.1.91: second developers snapshot of the 1.2 cycle
  • 1.1.99: release candidate of the 1.2 cycle
  • 1.2.0: first stable candidate of the 1.2 cycle
  • 1.2.90: first developers snapshot of the 1.3 cycle

Further considerations

Additional snapshots

If you are doing additional releases, you can either choose to increase the micro component—typically reserved for stable cycles; or you can append an additional “nano” component to the version tuple, e.g.

  • 1.1.alpha.1: second alpha snapshot of the 1.1 development cycle
  • 1.2.90.1: second alpha snapshot of the 1.3 development cycle

Version bumps

You are strongly encouraged to follow a policy of post-release version bump. In other words: the micro component of your project inside the source code repository should always be higher than the micro component of the archive you just released.

You have the option of using the next version, or—if you’re using a Cairo-like versioning scheme—you can use a separate identifier for the code in the source repository. The important part is that other projects can choose whether to depend on released or unreleased code.

Ancillary versioned files

Always remember that your pkg-config file and your inclusion path must be versioned to allow for parallel installability. You SHOULD follow the instructions in the maintainers guide.

4 Likes

A small addition that involves applications and AppStream metadata.

Whenever you release a new version of your project, you should add a release entry in your AppStream file. Unfortunately, the tool we use to validate the AppStream file—appstream-util validate—does not understand non-numeric components, and it’s not currently maintained. For development releases you have the following two options:

  1. do not update the AppStream metadata file for development releases, and instead write a comprehensive release entry for the first stable release
  2. use a different separator for the development releases; the recommended one is tilde (~)

For instance, if you are releasing 44.beta, you should add the following release element to your metadata:

<releases>
  <release version="44~beta" type="development" date="2022-02-11">
    <description>
      <p>Beta release</p>
    </description>
  </release>
</releases>

This is limited to the AppStream metadata file.

1 Like

What exactly is the consequence of that? I’m asking because metadata files with non-numerical version number pass validation, so I’m wondering whether the releases tag ends up being ignored or something.

The AppStream spec has its own version comparison algorithm mutuated from packaging tools, which requires using a tilde to sort alphabetical components before numerical components.

Sorting gets broken.

The main issue is that we have layers of validation that have no specification or documented behaviour:

  • appstream-util validate has its own rules
  • appstream-util validate on Flathub has similar rules, but since Flathub uses a special snapshot, they might not match at all
  • the AppStream spec (and appstreamcli) has its own rules, but those are much more relaxed that appstream-util’s ones; for instance: you can have any image as a screenshot, or no screenshot whatsoever, and the AppStream metainfo would still be considered valid from a spec standpoint, but it would not be accepted by appstream-util validate, which has specific (undocumented) requirements when it comes to screenshot images, their size, their content, and their caption

In practice, the AppStream situation is a concentrated mess, but it’s been a mess long before GNOME changed its versioning scheme anyway.