Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,35 @@
# [1]: https://cliutils.gitlab.io/modern-cmake/chapters/intro/dodonot.html
cmake_minimum_required(VERSION 3.27...3.29)

# Derive the project version from `au/version.hh`, which is the single source of truth for the
# library version (it also defines the `AU_VERSION*` macros for C++ code). This keeps the CMake
# version and the C++ macros from ever drifting apart, and means a release only needs to bump the
# version in one place.
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/au/version.hh" _au_version_hh)
string(REGEX MATCH "#define AU_VERSION_MAJOR ([0-9]+)" _ "${_au_version_hh}")
set(_au_version_major "${CMAKE_MATCH_1}")
string(REGEX MATCH "#define AU_VERSION_MINOR ([0-9]+)" _ "${_au_version_hh}")
set(_au_version_minor "${CMAKE_MATCH_1}")
string(REGEX MATCH "#define AU_VERSION_PATCH ([0-9]+)" _ "${_au_version_hh}")
set(_au_version_patch "${CMAKE_MATCH_1}")

# Fail loudly if any component failed to parse, rather than silently building a malformed version
# such as "0.5." (which would otherwise flow into `project()`, the docs URL, and installed configs).
foreach(_part major minor patch)
if(NOT _au_version_${_part} MATCHES "^[0-9]+$")
message(FATAL_ERROR
"Could not parse AU_VERSION_${_part} from au/version.hh. "
"Expected a line like '#define AU_VERSION_${_part} <number>'.")
endif()
endforeach()

set(AU_VERSION "${_au_version_major}.${_au_version_minor}.${_au_version_patch}")

project(
Au
VERSION 0.5.0
VERSION ${AU_VERSION}
DESCRIPTION "A C++ quantities and units library, by Aurora Innovation"
HOMEPAGE_URL "https://aurora-opensource.github.io/au/0.5.0/"
HOMEPAGE_URL "https://aurora-opensource.github.io/au/${AU_VERSION}/"
LANGUAGES CXX
)

Expand Down
55 changes: 40 additions & 15 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,29 @@ Any empty section can be omitted.
We try to follow [semantic versioning](https://semver.org/). Since we are currently in major
version zero (0.y.z), incompatible changes don't force a major version upgrade.

### Update the CMake version number

Edit the `CMakeLists.txt` file in the root folder, updating the version number in the `project`
command to the number chosen above.

Also update the version number in the `HOMEPAGE_URL` parameter, because we link to the docs for the
latest release in our CMake project definition. (True, this URL won't exist until you complete the
remaining steps in this guide, but the danger of getting it wrong is pretty small.)

Make a PR with these changes and land it before creating the tag.
### Update the version number

The version number lives in exactly one place: the `AU_VERSION_MAJOR`, `AU_VERSION_MINOR`, and
`AU_VERSION_PATCH` macros in `au/version.hh`. Edit those three macros to the number chosen above.
Everything else is derived from them:

- The C++ `AU_VERSION` macro (which downstream users read to detect the library and its version).
- The CMake `project(... VERSION ...)` and `HOMEPAGE_URL`, which the root `CMakeLists.txt` parses
out of `au/version.hh`. (The `HOMEPAGE_URL` points at the docs for this release, which won't
exist until you complete the remaining steps in this guide, but the danger of getting it wrong is
pretty small.)

**Where this commit lands depends on the release type:**

- **Minor or major release** (e.g. `0.6.0`): make a PR that bumps `au/version.hh` and land it on
`main` _before_ creating the tag. This is the "final commit", and it becomes the base commit for
the release branch (see "Prepare the release branch" below). Note that `main` will continue to
report this version until the _next_ minor/major bump, even as new (unreleased) changes land on
top of it; this is expected, and matches how the version macros are documented to behave.
- **Patch release** (`0.5.1` and later): the version bump does _not_ go on `main`. Patch releases
are cherry-picked from `main` onto the pre-existing release branch (e.g. `release-0.5.0`). Bump
`au/version.hh` in a commit _on the release branch_ alongside the cherry-picked fix(es), and tag
that. `main` keeps whatever version it already had.

### Fill out release notes template

Expand Down Expand Up @@ -142,9 +155,9 @@ Issues! Alphabetically:

### Prepare the release branch

First, make sure the "final commit" (which updates the CMake variables) has already landed, and is
currently checked out. This will be the "base" commit for the release branch, which we'll create
and push to GitHub.
First, make sure the "final commit" (which updates the version in `au/version.hh`) has already
landed, and is currently checked out. This will be the "base" commit for the release branch, which
we'll create and push to GitHub.

```sh
# Remember to update the version number!
Expand All @@ -155,7 +168,7 @@ git push origin release-0.3.1
Branches named similarly to `release-0.3.1` are protected in the Au repo, so we will need to make
PRs for the final changes for the release.

### PR: Update links
### PR: Update links and mark as a release

Several C++ files in the repository link to the documentation website, but they link to the version
at `main`. This version will change over time in ways that we can't predict. It's important for
Expand All @@ -172,7 +185,19 @@ https://aurora-opensource.github.io/au/0.3.1/...
^^^^^
```

We do this in a PR on the release branch in order to avoid churn commits on the main branch.
In this same PR, flip the release flag in `au/version.hh`:

```
Find this: #define AU_VERSION_IS_RELEASE 0
Replace with: #define AU_VERSION_IS_RELEASE 1
```

This value is `0` on `main` at all times, and this release-branch-only PR is the _only_ place it
becomes `1`. That's what lets downstream users distinguish an official release from an arbitrary
`main` checkout (whose version numbers may otherwise be identical to the release it was branched
from). It must never be set to `1` on `main`.

We do all of this in a PR on the release branch in order to avoid churn commits on the main branch.

### Create the tag for the release

Expand Down
17 changes: 17 additions & 0 deletions au/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,23 @@ cc_library(
name = "config",
hdrs = ["config.hh"],
visibility = ["//visibility:public"],
deps = [":version"],
)

cc_library(
name = "version",
hdrs = ["version.hh"],
visibility = ["//visibility:public"],
)

cc_test(
name = "version_test",
size = "small",
srcs = ["version_test.cc"],
deps = [
":version",
"@googletest//:gtest_main",
],
)

cc_library(
Expand Down
9 changes: 9 additions & 0 deletions au/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ header_only_library(
truncation_risk.hh
unit_of_measure.hh
unit_symbol.hh
version.hh
view.hh
wrapper_operations.hh
zero.hh
Expand Down Expand Up @@ -644,6 +645,14 @@ gtest_based_test(
testing
)

gtest_based_test(
NAME version_test
SRCS
version_test.cc
DEPS
au
)

gtest_based_test(
NAME zero_test
SRCS
Expand Down
4 changes: 4 additions & 0 deletions au/config.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

#pragma once

// Make the version macros (`AU_VERSION`, etc.) available anywhere `config.hh` reaches --- which is
// effectively the entire library, since the core machinery includes this header.
#include "au/version.hh"

//
// Device/GPU support (CUDA, HIP)
//
Expand Down
67 changes: 67 additions & 0 deletions au/version.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2026 Aurora Operations, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

//
// Version macros for the Au library.
//
// These serve two purposes. First, they let downstream code detect that Au has been included at
// all (for example, to `#error` if it has _not_ been): any Au header transitively includes this
// one, so `#if defined(AU_VERSION)` will be true whenever any part of Au is in scope. Second, they
// let downstream code detect _which version_ of Au is present, which is useful for writing code
// that must support multiple Au versions during a migration.
//
// The individual components are available as `AU_VERSION_MAJOR`, `AU_VERSION_MINOR`, and
// `AU_VERSION_PATCH`. For convenience, `AU_VERSION` combines them into a single integer that
// increases monotonically with the version, so that ordinary integer comparisons work:
//
// #if AU_VERSION < AU_VERSION_NUMBER(0, 5, 1)
// // ... code for Au older than 0.5.1 ...
// #endif
//
// IMPORTANT (release model): these numbers are only authoritative for _tagged releases_. Every Au
// release is cut from a dedicated release branch, and the released commit never lives on `main`
// (see `RELEASE.md`). On `main`, these macros name the _most recent release_, mirroring the
// version in the root `CMakeLists.txt` (which is derived from this file). That means a `main`
// checkout can report version `X.Y.Z` while actually containing changes made _after_ `X.Y.Z` was
// released. There is currently no programmatic way to tell such a `main` checkout apart from the
// tagged `X.Y.Z` release; they report the same version number.
//
// To keep the two build systems in sync, `CMakeLists.txt` parses the three component macros below
// to populate its `project(... VERSION ...)`. This file is therefore the single source of truth
// for the library version, and it is the _only_ place that needs to be edited when bumping the
// version for a release.
//

#define AU_VERSION_MAJOR 0
#define AU_VERSION_MINOR 5
#define AU_VERSION_PATCH 0

// Combine major/minor/patch components into a single monotonically increasing integer. Each
// component gets three decimal digits, so components must be strictly less than 1000.
#define AU_VERSION_NUMBER(major, minor, patch) ((major) * 1000000 + (minor) * 1000 + (patch))

#define AU_VERSION AU_VERSION_NUMBER(AU_VERSION_MAJOR, AU_VERSION_MINOR, AU_VERSION_PATCH)

// Whether this is an actual tagged release (`1`) or an in-development checkout (`0`).
//
// Because a minor/major release branch is cut _from_ the `main` commit that bumps the version, that
// commit's version numbers above are byte-identical to the release's --- so the version numbers
// alone cannot tell `main` apart from the release it was branched from. This flag closes that gap.
// It is `0` on `main` at all times, and is flipped to `1` only on the release branch (as part of
// the release-only step that also updates the doc links; see `RELEASE.md`). Downstream code that
// must be sure it is building against an official release, and not an arbitrary `main` checkout,
// can therefore test `#if AU_VERSION_IS_RELEASE`.
#define AU_VERSION_IS_RELEASE 0
72 changes: 72 additions & 0 deletions au/version_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2026 Aurora Operations, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "au/version.hh"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace {

using ::testing::AnyOf;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Gt;
using ::testing::Lt;

TEST(Version, ComponentMacrosAreDefined) {
#ifndef AU_VERSION_MAJOR
FAIL() << "AU_VERSION_MAJOR is not defined";
#endif
#ifndef AU_VERSION_MINOR
FAIL() << "AU_VERSION_MINOR is not defined";
#endif
#ifndef AU_VERSION_PATCH
FAIL() << "AU_VERSION_PATCH is not defined";
#endif
// Each component must fit in the three decimal digits that `AU_VERSION_NUMBER` allots it.
EXPECT_THAT(AU_VERSION_MINOR, Lt(1000));
EXPECT_THAT(AU_VERSION_PATCH, Lt(1000));
}

TEST(Version, CombinedVersionMatchesComponents) {
EXPECT_THAT(AU_VERSION,
Eq(AU_VERSION_MAJOR * 1000000 + AU_VERSION_MINOR * 1000 + AU_VERSION_PATCH));
}

TEST(Version, VersionNumberOrdersLikeSemanticVersioning) {
// Patch increments increase the number.
EXPECT_THAT(AU_VERSION_NUMBER(0, 5, 1), Gt(AU_VERSION_NUMBER(0, 5, 0)));
// Minor increments outweigh any patch difference.
EXPECT_THAT(AU_VERSION_NUMBER(0, 6, 0), Gt(AU_VERSION_NUMBER(0, 5, 999)));
// Major increments outweigh any minor difference.
EXPECT_THAT(AU_VERSION_NUMBER(1, 0, 0), Gt(AU_VERSION_NUMBER(0, 999, 999)));
}

TEST(Version, IncludingAnyAuHeaderDefinesVersion) {
// This is the "was Au included?" use case: `AU_VERSION` is truthy whenever Au is in scope.
EXPECT_THAT(AU_VERSION, Ge(AU_VERSION_NUMBER(0, 5, 0)));
}

TEST(Version, IsReleaseFlagIsDefinedAndBoolean) {
#ifndef AU_VERSION_IS_RELEASE
FAIL() << "AU_VERSION_IS_RELEASE is not defined";
#endif
// Note: we deliberately do _not_ assert that this is `0`. It is `0` on `main`, but `1` on the
// release branch (where CI also runs), so pinning the value would break release-branch builds.
// The "`main` must be `0`" invariant is a process guarantee (see `RELEASE.md`), not a test.
EXPECT_THAT(AU_VERSION_IS_RELEASE, AnyOf(Eq(0), Eq(1)));
}

} // namespace
3 changes: 3 additions & 0 deletions docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ Here's a guide to the main reference pages.

- **[Math functions](./math.md).** We provide many common mathematical functions out of the box.

- **[Version macros](./version.md).** Preprocessor macros (`AU_VERSION`, and friends) that let
downstream code detect that Au is present, and which version it is.

- **[Format support](./format.md).** Exercise fine-grained control over formatting `Quantity` and
`QuantityPoint` to strings, using either the popular [{fmt}] library, or C++20's `std::format`.

Expand Down
63 changes: 63 additions & 0 deletions docs/reference/version.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Version macros

Au provides preprocessor macros that let downstream code detect that the library is present, and
which version of it is present. They live in [`"au/version.hh"`](../../au/version.hh), which every
Au header transitively includes, so they are available whenever any part of Au is in scope --- and
also in the single-file packages, since those always include `au/au.hh`.

## Macros

- `AU_VERSION_MAJOR`, `AU_VERSION_MINOR`, `AU_VERSION_PATCH`: the three components of the version, as
integer literals.

- `AU_VERSION`: a single integer that increases monotonically with the version, so that ordinary
integer comparisons order versions correctly.

- `AU_VERSION_NUMBER(major, minor, patch)`: computes the `AU_VERSION`-style integer for an arbitrary
version, so you can compare against it. Each component must be strictly less than `1000`.

- `AU_VERSION_IS_RELEASE`: `1` if this is an official tagged release, and `0` for an in-development
(`main`) checkout. See ["`main` versus tagged releases"](#caveat-main-versus-tagged-releases)
below.

## Use cases

**Detecting that Au was included.** Because every Au header pulls in the version macros, you can
`#error` (or warn) if the library is _not_ in scope:

```cpp
#if !defined(AU_VERSION)
#error "This file requires the Au units library to be included first."
#endif
```

**Detecting the version.** This is useful when writing code that must support more than one Au
version during a migration:

```cpp
#if AU_VERSION < AU_VERSION_NUMBER(0, 5, 1)
// ... code path for Au older than 0.5.1 ...
#else
// ... code path for Au 0.5.1 and newer ...
#endif
```

## Caveat: `main` versus tagged releases

These macros are only authoritative for **tagged releases**. Every Au release is cut from a
dedicated release branch, and the released commit never lives on `main`. On `main`, the macros name
the _most recent release_ (they are kept in sync with the version in the root `CMakeLists.txt`,
which is derived from `au/version.hh`). That means a `main` checkout can report version `X.Y.Z`
while actually containing changes made _after_ `X.Y.Z` was released. In fact, because a
minor/major release branch is cut _from_ the `main` commit that bumps the version, that `main`
commit's three version numbers are byte-identical to the release's --- so the version numbers alone
cannot tell them apart.

Use `AU_VERSION_IS_RELEASE` to close that gap. It is `1` only on official tagged releases, and `0`
on `main`:

```cpp
#if !AU_VERSION_IS_RELEASE
#warning "Building against an in-development checkout of Au, not a tagged release."
#endif
```
Loading