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
56 changes: 17 additions & 39 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ jobs:
run: |
export DATE_FORMATTED=`date +"%Y.%-m.%-d"`
MINOR_VERSION="${{ github.event.inputs.minor_version }}"
VERSION="${DATE_FORMATTED}.${MINOR_VERSION:-${{ github.run_number }}}"
# Scheduled runs have no inputs, so the minor defaults. NEVER default
# to github.run_number: the 2026.6.25.641→2026.7.6.649 accidental
# series was scheduled runs publishing run-number versions. The date
# already makes a scheduled version unique; a same-day manual dispatch
# must choose its own minor_version (twine loudly rejects duplicates).
VERSION="${DATE_FORMATTED}.${MINOR_VERSION:-1}"
RUN_ATTEMPT="${{ github.run_attempt }}"
if [ "$RUN_ATTEMPT" -gt "1" ]
then
Expand All @@ -77,7 +82,7 @@ jobs:
if [ "${{ needs.resolve_mode.outputs.rehearsal }}" = "true" ]
then
DEV_SEGMENT=$(( ${{ github.run_number }} * 100 + RUN_ATTEMPT ))
VERSION="${DATE_FORMATTED}.${MINOR_VERSION:-${{ github.run_number }}}.dev${DEV_SEGMENT}"
VERSION="${DATE_FORMATTED}.${MINOR_VERSION:-1}.dev${DEV_SEGMENT}"
fi
export VERSION
echo "::set-output name=version_number::${VERSION}"
Expand Down Expand Up @@ -401,20 +406,23 @@ jobs:
run: |
git config --global user.email "richard@rghsoftware.co.uk"
git config --global user.name "GitHub Actions bot"
- name: Update version
- name: Stamp version in build tree
# Stamps the wheels only — the stamp is NOT committed back to the library
# repo. Daily "Update version to X" commits to every library main were the
# stale-CI/noise engine behind the June/July 2026 accidental-release
# cascade (PyAutoBuild#118 / #120); pip users get __version__ from the
# stamped wheel, source checkouts use PYAUTO_SKIP_WORKSPACE_VERSION_CHECK.
run: |
pushd "${{ matrix.project.path }}"
VERSION="${{ needs.version_number.outputs.version_number }}"
sed -i "s/__version__ = [\.\"\'0-9]*/__version__ = \"$VERSION\"/g" */__init__.py
git commit "-am 'Updated version in __init__ to $VERSION"
- name: Tag
run: |
pushd "${{ matrix.project.path }}"
VERSION="${{ needs.version_number.outputs.version_number }}"
git tag -m "Release $VERSION" -a "$VERSION"
PAT="${{ secrets[matrix.project.pat] }}"
git remote set-url --push origin "https://$PAT@github.com/${{ matrix.project.repository }}.git"
git push
git push --tags
- name: Build
run: |
Expand Down Expand Up @@ -544,13 +552,6 @@ jobs:
- name: Install Jupyter dependency
run: |
pip install jupyter ipynb-py-convert PyYAML
- name: Update version in README
run: |
VERSION="${{ needs.version_number.outputs.version_number }}"
PACKAGE="${{ matrix.workspace.package }}"
pushd workspace
sed -i "s/$PACKAGE v[0-9]\{4\}\.[0-9]*\.[0-9]*\.[0-9]*/$PACKAGE v$VERSION/g" README.rst README.md 2>/dev/null || true
git add README.rst README.md 2>/dev/null || true
- name: Update jupyter notebooks
if: "${{ matrix.workspace.generate_notebooks == true }}"
run: |
Expand All @@ -560,33 +561,10 @@ jobs:
python3 "$AUTOBUILD_PATH/generate.py" ${{ matrix.workspace.name }}
git add *.ipynb
git commit -m "Release ${{ needs.version_number.outputs.version_number }}: update notebooks and version" || true
- name: Write workspace version
run: |
VERSION="${{ needs.version_number.outputs.version_number }}"
pushd workspace
echo "$VERSION" > version.txt
python3 - "$VERSION" <<'PY'
import re, sys, pathlib
version = sys.argv[1]
path = pathlib.Path("config/general.yaml")
if not path.exists():
sys.exit(0)
text = path.read_text()
line_re = re.compile(r"^(?P<indent>\s*)workspace_version:\s*.*$", re.MULTILINE)
if line_re.search(text):
text = line_re.sub(lambda m: f"{m.group('indent')}workspace_version: {version}", text)
else:
block_re = re.compile(r"^version:\s*\n", re.MULTILINE)
if block_re.search(text):
text = block_re.sub(f"version:\n workspace_version: {version}\n", text, count=1)
else:
if not text.endswith("\n"):
text += "\n"
text += f"version:\n workspace_version: {version}\n"
path.write_text(text)
PY
git add version.txt config/general.yaml 2>/dev/null || git add version.txt
git commit -m "Release $VERSION: pin workspace version" || true
# The former "Update version in README" and "Write workspace version" steps
# are gone: workspace version pins are obsolete under the compatibility-floor
# check (PyAutoConf#118) — the workspace's `version.minimum_library_version`
# is bumped deliberately when scripts need new API, never by the release cron.
- name: Regenerate API audit baseline
if: "${{ matrix.workspace.write_api_baseline == true }}"
env:
Expand Down
4 changes: 2 additions & 2 deletions docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ This script does the following for each repo:

Before the per-repo loop, `pre_build.sh` invokes `admin_jammy/software/ensure_workspace_labels.sh` to assert the canonical `pending-release` label across every release-window repo (idempotent — a no-op when nothing has drifted).

Release-readiness checking is **not** Build's job — PyAutoBuild is a pure executor. The version-skew check that used to live here (`verify_workspace_versions.sh`, a fail-fast guard against a workspace pinned ahead of its installed library, or a `config/general.yaml` ↔ `version.txt` disagreement) now lives in **PyAutoHeart** as the `version_skew` check feeding `pyauto-heart readiness`. The PyAutoBrain release agent gates on `pyauto-heart readiness` before invoking `pre_build`; a human running `pre_build` directly is trusted to have checked readiness first. See PyAutoHeart for the resolution precedence (`config/general.yaml:version.workspace_version`, then `version.txt`) — identical to `autoconf.workspace.check_version`, so workspace/library mismatches still surface on every script run via each library's `__init__.py`.
Release-readiness checking is **not** Build's job — PyAutoBuild is a pure executor. The version-skew check that used to live here (`verify_workspace_versions.sh`, a fail-fast guard against a workspace pinned ahead of its installed library, or a `config/general.yaml` ↔ `version.txt` disagreement) now lives in **PyAutoHeart** as the `version_skew` check feeding `pyauto-heart readiness`. The PyAutoBrain release agent gates on `pyauto-heart readiness` before invoking `pre_build`; a human running `pre_build` directly is trusted to have checked readiness first. See PyAutoHeart for the resolution precedence (`config/general.yaml:version.workspace_version`, then `version.txt`) — mirroring `autoconf.workspace.check_version`. Since PyAutoBuild#120, releases no longer write workspace version pins or commit `__init__.py` stamps back to library mains (wheels are stamped at build time; tags are the release anchor): the runtime check enforces a compatibility **floor** (`version.minimum_library_version`, bumped deliberately — PyAutoConf#118), and Heart's `version_skew` check needs a follow-up rework to compare floors against release tags rather than stamp-vs-pin.

`generate.py` is run from the workspace root with `PYTHONPATH` pointing at `PyAutoBuild/autobuild/`. Only specific safe directories are committed — never `output/`, `output_model/`, or run-generated artefacts. After all workspaces are done, PyAutoBuild itself is committed and pushed, then `gh workflow run release.yml` dispatches the GitHub Actions release.

Expand Down Expand Up @@ -164,6 +164,6 @@ The workflow (`release.yml`) is manually dispatched with inputs:
executor with no ad-hoc skip levers or inline notebook-visualisation job. "Build without
releasing" is now exactly what `rehearsal` mode is for.)

`release.yml` is a **pure executor**: it builds, tests-the-install, publishes to PyPI, and commits generated notebooks + version pins to the workspaces. Workspace-integration validation (the old `find_scripts` / `generate_notebooks` / `run_scripts` / `run_notebooks` / `analyze_results` jobs) moved to **PyAutoHeart**'s `workspace-validation.yml`; release readiness is gated upstream by the PyAutoBrain release agent via `pyauto-heart readiness` before this workflow is dispatched. The `script_matrix.py` / `run_python.py` / `run.py` / `aggregate_results.py` primitives remain here and are checked out + reused by the Heart workflow.
`release.yml` is a **pure executor**: it builds, tests-the-install, publishes to PyPI, tags every library and workspace, and commits generated notebooks + Colab URL bumps to the workspaces (version stamps are build-tree-only since #120 — no `__init__.py` commit-backs to library mains, no workspace version pins). Workspace-integration validation (the old `find_scripts` / `generate_notebooks` / `run_scripts` / `run_notebooks` / `analyze_results` jobs) moved to **PyAutoHeart**'s `workspace-validation.yml`; release readiness is gated upstream by the PyAutoBrain release agent via `pyauto-heart readiness` before this workflow is dispatched. The `script_matrix.py` / `run_python.py` / `run.py` / `aggregate_results.py` primitives remain here and are checked out + reused by the Heart workflow.
The never-rewrite-history rules live in [`AGENTS.md`](../AGENTS.md) and apply
here as everywhere.