Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b5d9eb5
[FEATURE](ci) Branching strategy Phase 3 - PyPI and CodeArtifact publ…
lowlydba Aug 5, 2026
84574c8
[REFACTOR](ci) Use tj-actions/changed-files in detect-affected-packages
lowlydba Aug 5, 2026
d5c9f27
[REFACTOR](ci) Reorder detect-affected-packages steps so both Python …
lowlydba Aug 5, 2026
e975d12
[REFACTOR](ci) Extract diff-package-versions composite action
lowlydba Aug 5, 2026
99b1fd1
[REFACTOR](ci) Move package_versions.py into diff-package-versions
lowlydba Aug 5, 2026
357b1ca
[REFACTOR](docs) Polish CONTRIBUTING.md for the finished branching/re…
lowlydba Aug 5, 2026
47c7912
[FIX](ci) Add pull_request trigger to test-schema
lowlydba Aug 6, 2026
46b7e99
[FEATURE](ci) Read overture-releaser PEM from Secrets Manager
lowlydba Aug 10, 2026
8475a1b
fix(actions): treat a package's first version as a releasable bump
lowlydba Aug 12, 2026
1d2c94c
Merge branch 'main' into 509-devops-branching-strategy---phase-3
lowlydba Aug 12, 2026
fff7195
[REFACTOR](ci) Un-hoist pyspark expression generation into the packag…
lowlydba Aug 12, 2026
a2350e6
[FEATURE](ci) Add a workflow_dispatch dry-run to Test PyPI on release…
lowlydba Aug 12, 2026
6cd7c16
[REFACTOR](ci) Drop the per-environment reviewer gate on PyPI publish
lowlydba Aug 12, 2026
ee9e0f2
[FIX](ci) Correct release-publisher app name and pin publish checkout…
lowlydba Aug 12, 2026
f8e3b0e
Add changelog fragment for pyspark prebuild un-hoisting
lowlydba Aug 12, 2026
2f8a302
Expand changelog fragment with CI-only scope note
lowlydba Aug 12, 2026
e7fede1
Rename check-python-code's default resolution matrix cell to locked
lowlydba Aug 12, 2026
0c52008
[FIX](ci) Address review feedback on publish workflows
lowlydba Aug 14, 2026
3dd502d
Merge branch 'main' into 509-devops-branching-strategy---phase-3
lowlydba Aug 14, 2026
65bb3bb
Merge origin/main into 509-devops-branching-strategy---phase-3
Copilot Aug 24, 2026
c83e62c
Fix optional pyspark import guard behavior
Copilot Aug 24, 2026
184cf69
[FEATURE](ci) Add a gated real-PyPI priming path to release-publish's…
lowlydba Aug 24, 2026
e548436
Merge remote-tracking branch 'origin/509-devops-branching-strategy---…
lowlydba Aug 24, 2026
2e7332d
[REFACTOR](ci) Simplify the priming dispatch to reuse pypi-release-<p…
lowlydba Aug 24, 2026
5022321
Merge branch 'main' into 509-devops-branching-strategy---phase-3
lowlydba Aug 24, 2026
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
67 changes: 67 additions & 0 deletions .github/actions/detect-affected-packages/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Detect affected packages
description: >
Detects packages affected by a no-bump push to main, i.e. any file changed
under packages/<pkg>/ between two commits, excluding packages whose
pyproject.toml version was also bumped in the same range (those release via
release-trigger instead; see docs/versioning.md) and packages removed in the
range (nothing left to build).

Prerequisites: repo must be checked out with `fetch-depth: 0` so `before` is
reachable.

inputs:
before:
description: The base commit SHA to compare against (e.g. github.event.before).
required: true
after:
description: The head commit SHA to compare to. Defaults to the checked-out HEAD.
required: false
default: HEAD

outputs:
count:
description: Number of affected packages.
value: ${{ steps.filter.outputs.count }}
packages:
description: >
JSON array of affected package directory names, e.g.
["overture-schema-common"]. Suitable as a matrix input.
value: ${{ steps.filter.outputs.packages }}

runs:
using: composite
steps:
- name: List changed package directories
id: changed-dirs
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
with:
base_sha: ${{ inputs.before }}
sha: ${{ inputs.after }}
files: packages/**
dir_names: true
dir_names_max_depth: 2
matrix: true
Comment thread
lowlydba marked this conversation as resolved.

# Independent of the step above (neither reads the other's output); runs
# after it only so both feed the final filter step back to back.
- name: Diff package versions
id: diff
uses: ./.github/actions/diff-package-versions
with:
before: ${{ inputs.before }}
after: ${{ inputs.after }}

- name: Detect affected packages
id: filter
shell: bash
env:
# all_modified_files (not all_changed_files) so deletions count too: a
# file removed from a package without a version bump still changes
# the built wheel.
CHANGED_DIRS: ${{ steps.changed-dirs.outputs.all_modified_files }}
DIFF: ${{ steps.diff.outputs.diff }}
run: |
set -euo pipefail
jq -n --argjson version_diff "$DIFF" --argjson changed_dirs "$CHANGED_DIRS" \
'{version_diff: $version_diff, changed_dirs: $changed_dirs}' \
| python3 "${GITHUB_ACTION_PATH}/detect_affected_packages.py" >> "$GITHUB_OUTPUT"
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3

"""
Detect packages affected by a no-bump push to main.

Composes `package_versions.py diff`'s version-diff JSON with a JSON array of
changed package directories (tj-actions/changed-files' `dir_names` output) to
find packages whose directory changed without also changing their
pyproject.toml version. Those need an internal `.postN` build (see
docs/versioning.md). Packages with a version bump in the same range are
excluded because they release via `release-trigger` instead, and removed
packages are excluded because there is nothing left to build.

Reads a single JSON object from stdin: {"version_diff": [...], "changed_dirs":
[...]}. Run from the repository root:

jq -n \\
--argjson version_diff "$(python3 package_versions.py diff BEFORE AFTER)" \\
--argjson changed_dirs '["packages/overture-schema-common"]' \\
'{version_diff: $version_diff, changed_dirs: $changed_dirs}' \\
| python3 detect_affected_packages.py

Prints `$GITHUB_OUTPUT` lines on stdout (progress goes to stderr):

count=<n> Number of affected packages.
packages=<json> JSON array of affected package directory names, e.g.
["overture-schema-common"]. Suitable as a matrix input.

Exit status:
0 Success (including the no-affected case).
1 Malformed input: a changed_dirs entry isn't packages/<pkg>.
"""

import json
import sys


def _package_name(path: str) -> str:
"""Extract <pkg> from a packages/<pkg> changed-directory entry."""
parts = path.split("/", 1)
if len(parts) < 2:
raise ValueError(
f"Expected a 'packages/<pkg>' changed-directory entry, got {path!r}. "
"Check dir_names_max_depth on the changed-files step."
)
return parts[1]


def main() -> None:
payload = json.load(sys.stdin)
version_diff = payload["version_diff"]
changed_paths = payload["changed_dirs"]

bumped = {change["package"] for change in version_diff if change["after"] is not None}
removed = {change["package"] for change in version_diff if change["after"] is None}

changed = {_package_name(path) for path in changed_paths}
affected = sorted(changed - bumped - removed)

for package in sorted(changed):
if package in removed:
print(f"{package}: removed. Skipping.", file=sys.stderr)
elif package in bumped:
print(
f"{package}: version bumped, handled by release-trigger. "
"Skipping internal build.",
file=sys.stderr,
)
else:
print(f"{package}: changed, no bump -> internal build.", file=sys.stderr)

if not affected:
print("No affected packages (no unreleased changes to publish).", file=sys.stderr)

print(f"count={len(affected)}")
print(f"packages={json.dumps(affected)}")


if __name__ == "__main__":
main()
14 changes: 6 additions & 8 deletions .github/actions/detect-version-bumps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,14 @@ runs:
steps:
- name: Diff package versions
id: diff
shell: bash
env:
BEFORE: ${{ inputs.before }}
run: |
python3 ./.github/workflows/scripts/package_versions.py diff "$BEFORE" HEAD \
> "${RUNNER_TEMP}/package-version-diff.json"
uses: ./.github/actions/diff-package-versions
with:
before: ${{ inputs.before }}

- name: Filter to releasable bumps
id: filter
shell: bash
env:
DIFF: ${{ steps.diff.outputs.diff }}
run: |
python3 "${GITHUB_ACTION_PATH}/detect_version_bumps.py" \
< "${RUNNER_TEMP}/package-version-diff.json" >> "$GITHUB_OUTPUT"
echo "$DIFF" | python3 "${GITHUB_ACTION_PATH}/detect_version_bumps.py" >> "$GITHUB_OUTPUT"
17 changes: 12 additions & 5 deletions .github/actions/detect-version-bumps/detect_version_bumps.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
- Released versions must be plain `<major>.<minor>.<patch>`; PEP 440
variants like `1.2.3rc4` fail loudly.
- A version decrease fails: it must never land on main.
- Added packages (`before` null) and removed packages (`after` null) are
not releases; they are skipped.
- Added packages (`before` null) count as a bump: a package's first
version releases to PyPI like any other. Removed packages (`after`
null) are not releases; they are skipped.

The `detect-version-bumps` action composes this with `package_versions.py`,
which owns reading versions from git (and enforces the major-bump cascade
Expand Down Expand Up @@ -64,13 +65,19 @@ def main() -> None:
before_raw = change["before"]
after_raw = change["after"]

if before_raw is None or after_raw is None:
info(f"{package}: added or removed, not a release. Skipping.")
if after_raw is None:
info(f"{package}: removed, not a release. Skipping.")
continue

before = semver(package, before_raw)
after = semver(package, after_raw)

if before_raw is None:
info(f"{package}: new package at {after_raw} (bump)")
bumps.append({"package": package, "version": after_raw, "tag": f"{package}-v{after_raw}"})
continue

before = semver(package, before_raw)

if after < before:
errors.append(f"{package}: {before_raw} -> {after_raw}")
continue
Expand Down
46 changes: 46 additions & 0 deletions .github/actions/diff-package-versions/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Diff package versions
description: >
Diffs packages/*/pyproject.toml versions between two commits (see
package_versions.py), reading blobs directly from git. Enforces the
major-bump cascade: a package whose direct workspace dependency takes a
major bump must take one itself; the action fails otherwise.

Prerequisites: repo must be checked out with `fetch-depth: 0` so `before`
is reachable.

inputs:
before:
description: The base commit SHA to compare against (e.g. github.event.before).
required: true
after:
description: The head commit SHA to compare to. Defaults to the checked-out HEAD.
required: false
default: HEAD

outputs:
count:
description: Number of packages with a version change (added, removed, or bumped).
value: ${{ steps.diff.outputs.count }}
diff:
description: >
JSON array of {"package", "before", "after"} objects, topologically
sorted (dependencies before dependents). `before`/`after` are null when
the package didn't exist at that commit.
value: ${{ steps.diff.outputs.diff }}

runs:
using: composite
steps:
- name: Diff package versions
id: diff
shell: bash
env:
BEFORE: ${{ inputs.before }}
AFTER: ${{ inputs.after }}
run: |
python3 "${GITHUB_ACTION_PATH}/package_versions.py" diff "$BEFORE" "$AFTER" \
> "${RUNNER_TEMP}/package-version-diff.json"
{
echo "count=$(jq 'length' "${RUNNER_TEMP}/package-version-diff.json")"
echo "diff=$(jq -c . "${RUNNER_TEMP}/package-version-diff.json")"
} >> "$GITHUB_OUTPUT"
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"""
Diff per-package versions between two git commits.

Run from the repository root:
Run with the repository root as the working directory (git commands need it
as CWD); the script itself can live anywhere, e.g. the `diff-package-versions`
action invokes it via `$GITHUB_ACTION_PATH`:

python3 package_versions.py diff <before-commit> <after-commit>
python3 path/to/package_versions.py diff <before-commit> <after-commit>

Reads each `packages/*/pyproject.toml` blob directly from git at both commits
(no checkout switching, no environment sync) and prints the packages whose
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/check-python-code.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ jobs:
strategy:
fail-fast: false
matrix:
# Default resolution exercises the committed lock against every
# supported Python minor version. The lowest-direct cell pins each
# direct dependency to its declared floor (see UV_RESOLUTION below)
# and runs only on the Python floor, since the resolved-low pyspark
# 3.4 wheels exist for 3.10/3.11 only.
# locked exercises the committed lock against every supported Python
# minor version. The lowest-direct cell pins each direct dependency
# to its declared floor (see UV_RESOLUTION below) and runs only on
# the Python floor, since the resolved-low pyspark 3.4 wheels exist
# for 3.10/3.11 only.
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
resolution: [default]
resolution: [locked]
include:
- python: "3.10"
resolution: lowest-direct
Expand Down Expand Up @@ -80,11 +80,11 @@ jobs:
run: echo "UV_RESOLUTION=lowest-direct" >> "$GITHUB_ENV"

# Fail fast if uv.lock is stale (e.g. a pyproject.toml version bump
# landed without a matching `uv lock` run). Only for the default
# landed without a matching `uv lock` run). Only for the locked
# resolution cells -- lowest-direct intentionally re-resolves away
# from the committed lock, so `--locked` would always fail there.
- name: Configure lock check
if: matrix.resolution == 'default'
if: matrix.resolution == 'locked'
run: echo "UV_LOCKED=1" >> "$GITHUB_ENV"

- name: Run make check
Expand Down
Loading
Loading