Skip to content
Open
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
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ jobs:
# The published version comes from src/coreai_opt/_about.py, not the tag.
# Fail early if they disagree so we never publish a mismatched/duplicate
# version (PyPI uploads are immutable and cannot be overwritten).
# `print_version.py --release` computes the version exactly as the
# `make version` computes the version exactly as the
# `make build` step below does, so this guard can't drift from what
# actually gets published.
# Skipped on manual dry runs, where the ref is a branch, not a vX.Y.Z tag.
# Run via uv (installed above) so the interpreter satisfies
# requires-python whatever the runner image ships; see the Makefile's
# `version` target.
# The target runs `uv run --no-project` internally, so uv (installed
# above) supplies an interpreter satisfying requires-python whatever the
# runner image ships; see the Makefile's `version` target.
if: github.event_name == 'push'
run: |
tag="${GITHUB_REF_NAME}"
version="$(uv run --no-config --no-project --python '>=3.11' scripts/make/print_version.py --release)"
version="$(make version)"
echo "tag=${tag} package version=${version}"
if [ "${tag}" != "v${version}" ]; then
echo "::error::Tag ${tag} does not match package version v${version} (src/coreai_opt/_about.py). Update latest_released_version so the release it implies matches the tag."
Expand Down
7 changes: 4 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,10 @@ repos:
name: Check _about.py version fields
description: |
Check that _about.py's latest_released_version matches the repo's
latest release tag, and that __version__ is its last number plus
one, plus .dev0. Catches a release candidate that looks like a
release has already shipped when it hasn't.
latest release tag (or has a release/<version> branch), and that
__version__ raises exactly one of its numbers by one, zeroes the
rest, and ends in .dev0. Catches a release candidate that looks
like a release has already shipped when it hasn't.
entry: python scripts/pre_commit/check_about_version.py
language: system
files: (^|/)_about\.py$
Expand Down
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initial release of `coreai-opt`. See the [GitHub Releases](https://github.com/apple/coreai-optimization/releases/) page for release notes.

[0.2.1]: https://github.com/apple/coreai-optimization/releases/tag/v0.2.1
[0.2.0]: https://github.com/apple/coreai-optimization/releases/tag/v0.2.0
[0.2.0]: https://github.com/apple/coreai-optimization/commits/v0.2.0/
[0.2.1]: https://github.com/apple/coreai-optimization/compare/v0.2.0...v0.2.1/
18 changes: 12 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Use of this source code is governed by a BSD-3-Clause license that can
# be found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause

.PHONY: _maybe_patch_pyproject all api-list build build-dev check clean distclean distclean-all docs docs-clean docs-open env env-all env-docs env-highest-torch env-lowest-torch env-tutorial render-api-index set-auto-venv test test-cov test-fast test-highest-pytorch test-lowest-pytorch test-slow test-smoke test-tutorials version
.PHONY: _maybe_patch_pyproject all api-list build build-dev check clean distclean distclean-all docs docs-clean docs-open env env-all env-docs env-highest-torch env-lowest-torch env-tutorial render-api-index set-auto-venv test test-cov test-fast test-highest-pytorch test-lowest-pytorch test-slow test-smoke test-tutorials version version-dev

SHELL := /bin/bash

Expand Down Expand Up @@ -356,12 +356,18 @@ distclean-all:
set-auto-venv:
@$(SCRIPTS)/make/set_auto_venv.sh $(DEFAULT_VENV) $(SHELL_RC)

# Show the development version carried on the tree (e.g. 0.2.2.dev0), including
# any COREAI_OPT_VERSION_EXTENSION (e.g. 0.2.2.1.dev0). Reads _about.py as plain
# text, so no venv is needed — but `uv run --no-project` is still what guarantees
# a >= 3.11 interpreter (a bare `python3` is 3.9 on stock macOS, and `python` may
# not exist at all) without requiring `make env` first.
# Show the version a release would publish (e.g. 0.2.2), including any
# COREAI_OPT_VERSION_EXTENSION (e.g. 0.2.2.1). This is the same computation the
# release workflow's tag guard uses, so the two cannot drift. Reads _about.py as
# plain text, so no venv is needed — but `uv run --no-project` is still what
# guarantees a >= 3.11 interpreter (a bare `python3` is 3.9 on stock macOS, and
# `python` may not exist at all) without requiring `make env` first.
version:
@uv run --no-config --no-project --python '>=3.11' $(SCRIPTS)/make/print_version.py --release

# Show the development version carried on the tree (e.g. 0.2.2.dev0), i.e.
# `version` with the .dev0 marker the tree carries between releases.
version-dev:
@uv run --no-config --no-project --python '>=3.11' $(SCRIPTS)/make/print_version.py

# =============================================================================
Expand Down
89 changes: 72 additions & 17 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,92 @@
# Package Release Guide

The OSS release process for Core AI Optimization is still being defined. This page will document the workflow for publishing to PyPI once the public release infrastructure is finalized.
Releases are cut and published by the [release managers team](https://github.com/orgs/apple/teams/coreai-optimization-release-managers).

The following commands are available locally:

```bash
make build # build the canonical, publishable wheel + sdist (uv build --no-sources)
make build-dev # build a timestamped dev wheel (e.g. 0.2.2.dev202607231430+abc1234)
make version # show the development version carried on the tree (e.g. 0.2.2.dev0)
make build # build the artifacts for the current release to be published
make build-dev # build a nightly or local development wheel (e.g. 1.1.0.dev202607231430+abc1234)
make version # show the version a release would publish (e.g. 1.1.0)
make version-dev # show the development version carried on the tree (e.g. 1.1.0.dev0)
make clean # remove build artifacts
```

## Version scheme

`main` always carries the version planned for the _next_ release. This ensures that ongoing development is never mistaken for an already-published version, and that a release can be stabilized, tested, and published on its own branch, independently of later changes on `main`. (The release-branch workflow itself — branch naming, tagging, and backporting fixes to `main` — will be documented separately in the release schedule doc; this section covers only the version-string mechanics.)
`main` always carries the version of the *next* release, never the one that already shipped.

`src/coreai_opt/_about.py` stores `latest_released_version` (the last tagged release, e.g. `"0.2.1"`) and computes `__version__` from it by incrementing its last number by one and adding a `.dev0` suffix (e.g. `"0.2.2.dev0"`). A pre-commit hook (`check-about-version`) verifies that `__version__` always follows this rule and that `latest_released_version` matches the repo's latest release tag. As a result, `__version__` can never look as though a release has shipped when it hasn't. The `.dev0` suffix is only a marker on the tree; it never appears in a built wheel.
There are three version formats:

- `make build` builds the release that `__version__` implies, e.g. `0.2.2`. A release is cut by tagging it (`v0.2.2`); `latest_released_version` is then hard-coded to `"0.2.2"`, which bumps `__version__` to the next candidate (`0.2.3.dev0`).
- `make build-dev` builds that same release but with a unique `.dev<UTC-timestamp>+<short-sha>` suffix instead. It is used by contributors, smoke tests, and the nightly pipeline. `DEV_VERSION=<version>` uses that version exactly instead.
| Format | Example | What it is |
| -------------------------------------- | ------------------------------- | -------------------------------------------- |
| `X.Y.Z` | `1.0.0` | a published release |
| `X.Y.Z.dev0` | `1.1.0.dev0` | the version `main` carries in the repo |
| `X.Y.Z.dev<UTC-timestamp>+<short-sha>` | `1.1.0.dev202607231430+abc1234` | a dev artifact, built from a specific commit |

Sorting is preserved: `0.2.2.dev0 < 0.2.2.dev202607231430+abc1234 < 0.2.2`.
Say `1.0.0` has just been released. `main` then carries `1.0.1.dev0`. That reads as "working toward a release after `1.0.0`, which has not shipped": the `.dev0` suffix marks the tree as unreleased and never appears in a built wheel. Nothing on `main` can be mistaken for a published version.

### Extending the scheme downstream
`main`'s `.dev0` always defaults to the last digit plus one, so after `1.0.0` it is `1.0.1.dev0`. Once the version of the next release is known — usually a minor — a PR sets `__version__` to it before the release branch is cut.

The flow below traces one cycle. At the cut, `main` and the release branch diverge and never rejoin: the branch keeps the version it was cut with, and only `main` moves on.

```mermaid
---
title: Version change flow
---
flowchart TB
prev["previous release schedule"] --> m1["main: 1.0.1.dev0<br/>placeholder, last digit + 1"]
prev --> r0("1.0.0 released")
m1 -->|"ready for release"| ask{"is the placeholder the<br/>version we want?"}
ask -->|"No, usually a minor"| pr["PR sets __version__"]
ask -->|Yes| fin
pr --> fin["main: 1.1.0.dev0<br/>assume we release 1.1.0;<br/>it could also stay 1.0.1"]
fin --> nxt["main: 1.1.1.dev0<br/>placeholder, last digit + 1"]
fin -->|cut| rb["release/1.1.0<br/>set latest_released_version = 1.1.0"]
rb --> stab["stabilize"]
stab -->|"tag v1.1.0"| rel("1.1.0 released")
nxt --> nextsched["next release schedule"]
rel --> nextsched
nextsched -.->|"the process repeats"| prev
```

`src/coreai_opt/_about.py` holds the last released version and `__version__`. Both are set together in one PR when `main` moves forward: the last released version becomes the release just branched, and `__version__` becomes the one after it. The `check-about-version` pre-commit hook enforces the relation between them: `__version__` must be the last released version with exactly one of its numbers raised by one, every number after that reset to zero, and `.dev0` on the end. From `1.0.0` it accepts `1.0.1.dev0`, `1.1.0.dev0`, or `2.0.0.dev0`, and nothing else. Any other value fails the commit, and the message prints the accepted ones, so there is nothing to work out by hand.

- `make build` builds the artifacts for the current release to be published.
- `make build-dev` builds the wheel for the nightly build, and local wheels for development and testing, each carrying a unique `.dev<UTC-timestamp>+<short-sha>` suffix.

Therefore, we have the following order:

```text
1.1.0.dev0 < 1.1.0.dev202607231430+abc1234 < 1.1.0
```

This is the order we want. `1.1.0.dev0` is the bare marker `main` carries, so it sorts below every wheel actually built for `1.1.0`. Each nightly sorts above it, and above the nightly before it, because the timestamp only grows. The published `1.1.0` sorts highest of all, so installers pick it over any dev wheel.

A release branch is the one place where the two match: it sets `latest_released_version` to the release it produces, so `__version__` is that same version plus `.dev0` rather than a next candidate. On `main` they always differ, which is what tells a release branch apart — and what lets a repo that vendors this one pin to a release branch and still resolve the right baseline.

`__version__` must always be a literal string, never an expression.

A repo that uses this one as a submodule and includes this `Makefile` — building one combined wheel from both trees — can add its own 4th number. Set `COREAI_OPT_VERSION_EXTENSION` to the number it's about to release next (e.g. `"1"` for its first release off a given OSS release, then `"2"` for the one after that). Then call `make build`, `make build-dev`, or `make version` unchanged:
### Release branches

1. `release/<version>` is created from `main`, once the version of the next release has been decided — that is, which digit gets one added to it. Its first commit sets `latest_released_version` to that version, so the branch names its own release.
2. The tag is created on the `release/<version>` branch, never on `main`.
3. After the cut, `main` continues on to the next release's `.dev0`.
4. The `check-about-version` pre-commit hook enforces the version rules on every commit.
5. After the cut, the release branch takes no new commits, unless a must-fix issue comes up. Those commits are later cherry-picked back to `main`.

Cut the branch before moving `main` to the next dev release.

### Extending the scheme downstream

- `latest_released_version` `"0.2.1"` + extension `"1"` -> candidate: `0.2.1.1.dev0`
- `make build` -> `0.2.1.1`
- `make build-dev` -> `0.2.1.1.dev<UTC-timestamp>+<short-sha>`
A repo that uses this one as a submodule and includes this `Makefile` — building one combined wheel from both trees — can add its own 4th number. Set `COREAI_OPT_VERSION_EXTENSION` to the number it's about to release next, then call `make build`, `make build-dev`, or `make version` unchanged.

The extra number is used exactly as given (`scripts/release/release_utils.apply_version_extension`); `latest_released_version`'s own last number is only bumped for OSS's own `main`, when no extension is set.
The extension anchors the release to the last *published* release instead of the one `__version__` is working toward:

There is still only one `_about.py` (this package's own); the extra number is a plain string handled entirely in `scripts/release/release_utils.next_release_base` — no other file or package is involved.
| `latest_released_version` | `__version__` | `COREAI_OPT_VERSION_EXTENSION` | Release built |
| ------------------------- | ------------- | ------------------------------ | ------------- |
| `1.0.0` | `1.1.0.dev0` | unset | `1.1.0` |
| `1.0.0` | `1.1.0.dev0` | `1` | `1.0.0.1` |
| `1.0.0` | `1.1.0.dev0` | `2` | `1.0.0.2` |

<!-- TODO: Document the chosen OSS release workflow (PyPI trusted publishing, twine upload, or uv publish). -->
The extension is used exactly as given, and starts at `1`, not `0`. `make build-dev` adds the usual `.dev<UTC-timestamp>+<short-sha>` suffix on top.
12 changes: 6 additions & 6 deletions scripts/make/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@
version. Called by `make build-dev`.

``_about.py`` stores ``latest_released_version`` (the last tagged release) by
hand; ``__version__`` is computed from it — add one to its last number, add
``.dev0``. This script computes the version to build from
``latest_released_version``, not from the on-tree ``__version__`` (which must
never be treated as already released), writes it into ``_about.py``, builds,
then restores the file. A repo that uses this one as a submodule (building
hand; ``__version__`` names the release the tree is working toward. This
script takes the version to build from ``__version__``, writes it into
``_about.py``, builds, then restores the file. A repo that uses this one as a submodule (building
one combined wheel) can add its own extra number to the version with
``COREAI_OPT_VERSION_EXTENSION``; see
``scripts/release/release_utils.next_release_base``.
Expand Down Expand Up @@ -89,7 +87,9 @@ def main() -> None:
repo_root = _find_repo_root(Path(__file__))

about = read_about(repo_root)
release_base = next_release_base(about.latest_released_version, get_version_extension())
release_base = next_release_base(
about.latest_released_version, about.version, get_version_extension()
)
build_version = resolve_build_version(
release_base,
dev=args.dev,
Expand Down
8 changes: 5 additions & 3 deletions scripts/make/print_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

Usage:
print_version.py the ``.dev0`` candidate, e.g. ``0.2.2.dev0``
(``make version``)
(``make version-dev``)
print_version.py --release the release itself, e.g. ``0.2.2`` — the
version ``make build`` publishes

Expand All @@ -25,7 +25,7 @@
import sys
from pathlib import Path

# `make version` exports PYTHONPATH, but the release workflow runs this script
# `make version-dev` exports PYTHONPATH, but the release workflow runs this script
# directly, which puts only `scripts/make/` on sys.path. Walk up to the project
# root (the directory holding pyproject.toml, alongside `scripts/`) so this
# keeps working if the script moves. It can't call
Expand Down Expand Up @@ -58,7 +58,9 @@ def main() -> None:

about = read_about(_repo_root)
compute = next_release_base if args.release else next_candidate_version
sys.stdout.write(f"{compute(about.latest_released_version, get_version_extension())}\n")
sys.stdout.write(
f"{compute(about.latest_released_version, about.version, get_version_extension())}\n"
)


if __name__ == "__main__":
Expand Down
Loading