Skip to content
Merged
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
14 changes: 14 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
changelog:
categories:
- title: Compatibility-significant changes
labels:
- compatibility
- schema
- generated-runtime
- conformance
- title: Fixes
labels:
- bug
- title: Other changes
labels:
- "*"
58 changes: 58 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: CI

on:
pull_request:
push:
branches:
- master

jobs:
test-and-build:
name: Test, build, and prove artifact install
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Set up uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install development dependencies
run: uv sync --all-extras --dev

- name: Run tests
run: uv run pytest

- name: Run lint
run: uv run ruff check src tests

- name: Build package artifacts
run: uv build

- name: Prove wheel installs outside the source tree
run: |
python -m venv .artifact-venv
. .artifact-venv/bin/activate
python -m pip install --upgrade pip
python -m pip install dist/*.whl
cd "$(mktemp -d)"
python - <<'PY'
import importlib.metadata
import command_generation

print(importlib.metadata.version("command-generation"))
print(command_generation.command_package_schema_path())
PY

- name: Upload package artifacts
uses: actions/upload-artifact@v4
with:
name: command-generation-dist
path: dist/*
76 changes: 76 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Release

on:
push:
tags:
- "v*.*.*"

permissions:
contents: write

jobs:
release:
name: Build and publish semver artifacts
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Set up uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Validate tag matches package version
run: |
PACKAGE_VERSION="$(python - <<'PY'
import tomllib

with open("pyproject.toml", "rb") as handle:
print(tomllib.load(handle)["project"]["version"])
PY
)"
TAG_VERSION="${GITHUB_REF_NAME#v}"
if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then
echo "Tag ${GITHUB_REF_NAME} does not match pyproject version ${PACKAGE_VERSION}" >&2
exit 1
fi

- name: Install development dependencies
run: uv sync --all-extras --dev

- name: Run tests
run: uv run pytest

- name: Run lint
run: uv run ruff check src tests

- name: Build package artifacts
run: uv build

- name: Prove wheel installs outside the source tree
run: |
python -m venv .artifact-venv
. .artifact-venv/bin/activate
python -m pip install --upgrade pip
python -m pip install dist/*.whl
cd "$(mktemp -d)"
python - <<'PY'
import importlib.metadata
import command_generation

print(importlib.metadata.version("command-generation"))
print(command_generation.command_package_schema_path())
PY

- name: Publish GitHub release
uses: softprops/action-gh-release@v2
with:
files: dist/*
generate_release_notes: true
fail_on_unmatched_files: true
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,6 @@ uv run python tests/primitive_conformance.py

This package should become a semver-tagged maintainer dependency with CI-built wheel/sdist artifacts. Downstream repositories should be able to consume immutable package versions instead of Git source refs. Until that release path is in place, source installs may remain a development fallback, but generated runtimes still must not import this package at runtime.

Pull request CI builds wheel and sdist artifacts and proves the built wheel can be installed outside the source tree. Semver releases are created from `vMAJOR.MINOR.PATCH` tags, validate that the tag matches `pyproject.toml`, and attach the built artifacts to the GitHub Release.

See `docs/release-and-versioning.md` for the intended package and compatibility model.
8 changes: 8 additions & 0 deletions docs/release-and-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ The ordinary downstream path should be:

GitHub source installs are acceptable during development and transition, but they should not remain the ordinary compatibility signal.

## CI And Release Mechanics

Pull request CI builds the package with `uv build`, uploads the `dist/` artifacts, and installs the built wheel into a fresh virtual environment from outside the source tree. That install proof is the ordinary guard against accidentally relying on editable-source imports, untracked files, or repository layout.

Semver releases are cut by pushing a `vMAJOR.MINOR.PATCH` tag. The release workflow validates that the tag version matches `project.version` in `pyproject.toml`, reruns tests and lint, rebuilds wheel/sdist artifacts, proves wheel installation from `dist/`, and attaches the artifacts to the GitHub Release.

Release notes are generated from merged PRs and classify compatibility-significant changes separately. PRs that change the command package IR schema, generated runtime layout, conformance semantics, target extension contract, or primitive behavior should use a compatibility label such as `schema`, `generated-runtime`, `conformance`, or `compatibility`.

## Compatibility Signals

Release notes should call out changes that affect consumers:
Expand Down
38 changes: 38 additions & 0 deletions tests/test_release_workflows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations

from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]


def test_ci_builds_and_proves_install_from_package_artifact() -> None:
workflow = (ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")

assert "uv build" in workflow
assert "python -m pip install dist/*.whl" in workflow
assert "cd \"$(mktemp -d)\"" in workflow
assert "import command_generation" in workflow
assert "actions/upload-artifact" in workflow


def test_release_workflow_publishes_semver_tag_artifacts() -> None:
workflow = (ROOT / ".github" / "workflows" / "release.yml").read_text(encoding="utf-8")

assert '"v*.*.*"' in workflow
assert "TAG_VERSION=\"${GITHUB_REF_NAME#v}\"" in workflow
assert "Tag ${GITHUB_REF_NAME} does not match pyproject version" in workflow
assert "uv build" in workflow
assert "python -m pip install dist/*.whl" in workflow
assert "softprops/action-gh-release" in workflow
assert "files: dist/*" in workflow
assert "generate_release_notes: true" in workflow


def test_release_notes_classify_compatibility_significant_changes() -> None:
release_config = (ROOT / ".github" / "release.yml").read_text(encoding="utf-8")

assert "Compatibility-significant changes" in release_config
assert "schema" in release_config
assert "generated-runtime" in release_config
assert "conformance" in release_config
Loading