Skip to content

Commit 1a87293

Browse files
stonebigclaude
andcommitted
cycle workflow: resolve each leg in cycle_config.py
The build job no longer re-derives lockfile names, build location, WINPYVER and artifact name per matrix leg: the config job hands them over. That removes the derivation step and the eight lockfile guards it fed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DePo4xZA8cKDkHEzbPjHmo
1 parent f9a0f61 commit 1a87293

2 files changed

Lines changed: 66 additions & 119 deletions

File tree

‎.github/scripts/cycle_config.py‎

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
"""Read a cycle TOML and emit its build configuration as GITHUB_OUTPUT lines.
22
3-
python .github/scripts/cycle_config.py cycles/2026_03.toml 3.14
3+
python .github/scripts/cycle_config.py cycles/2026_04.toml 3.14
44
55
Writes to $GITHUB_OUTPUT when set, otherwise stdout, so it can be run locally
66
to see exactly what a workflow run would get. tomllib is stdlib from 3.11, and
77
GitHub runners are newer than that, so this needs no dependency.
88
9-
The emitted matrix holds only the flavors this Python can actually build: the
10-
right architecture, and a pylock file present in the cycle directory. A flavor
11-
the cycle declares but has no lockfile for -- `whl` today -- costs nothing, so
12-
it can stay declared until it comes back. Paths are relative to the working
13-
directory, which in CI is the checkout root.
9+
Everything the build job needs is decided here rather than re-derived per
10+
matrix leg in PowerShell:
11+
12+
* which flavors this Python can build -- right architecture, and a pylock
13+
present in the cycle directory. A flavor the cycle declares but has no
14+
lockfile for costs nothing, so it can stay declared until it comes back.
15+
* the file names those flavors use, so a leg never has to look one up.
16+
* a cycle with no lockfiles at all fails here, loudly, instead of starting
17+
runners whose every step is then skipped.
18+
19+
Paths are relative to the working directory, which in CI is the checkout root.
1420
"""
1521
import json
1622
import os
@@ -19,25 +25,34 @@
1925
from pathlib import Path
2026

2127

22-
def buildable_flavors(cfg: dict, requested: str, ver2: str) -> list[dict]:
23-
"""Flavors with the right architecture and a lockfile on disk.
28+
def flavor_entry(cfg: dict, flavor: dict, ver2: str, python_version: str) -> dict | None:
29+
"""One matrix leg, or None when this flavor has no lockfile to build.
2430
25-
Same two conditions the build job used to re-test in PowerShell, one per
26-
matrix leg. Deciding here instead means a leg with nothing to do never
27-
starts a runner, and a cycle with no lockfiles at all fails loudly rather
28-
than going green having built nothing.
31+
Names follow the layout the publish step writes: pylock.64-<ver2 with
32+
underscores><flavor><release level>.toml, plus the _wheels variants the
33+
wheelhouse flavor adds.
2934
"""
30-
arch = "64F" if requested.endswith("F") else "64"
3135
cycle_dir = Path(cfg["cycle_dir"])
3236
level = cfg.get("release_level", "")
33-
tag = ver2.replace(".", "_")
34-
kept = []
35-
for flavor in cfg["flavors"]:
36-
if str(flavor.get("WINPYARCHDET", "")) != arch:
37-
continue
38-
if (cycle_dir / f"pylock.64-{tag}{flavor['name']}{level}.toml").is_file():
39-
kept.append(flavor)
40-
return kept
37+
stem = f"64-{ver2.replace('.', '_')}{flavor['name']}{level}"
38+
39+
lockfile = cycle_dir / f"pylock.{stem}.toml"
40+
if not lockfile.is_file():
41+
return None
42+
43+
def optional(path: Path) -> str:
44+
return path.as_posix() if path.is_file() else ""
45+
46+
return {
47+
"name": flavor["name"],
48+
"PANDOC": flavor["PANDOC"],
49+
"formats": flavor["formats"],
50+
"lockfile": lockfile.as_posix(),
51+
"lockfile_wheels": optional(cycle_dir / f"pylock.{stem}_wheels.toml"),
52+
"requirements_wheels": optional(cycle_dir / f"requir.{stem}_wheels.txt"),
53+
"winpyver": f"{ver2}{flavor['name']}{level}",
54+
"artifact_name": f"publish_{python_version}{flavor['name']}",
55+
}
4156

4257

4358
def build_config(cfg: dict, requested: str) -> dict:
@@ -50,24 +65,39 @@ def build_config(cfg: dict, requested: str) -> dict:
5065

5166
# the check the PowerShell version did: ver2's first 3 parts must appear in
5267
# the tarball URL, so a copy-paste slip between the two cannot go unnoticed
53-
short = ".".join(entry["ver2"].split(".")[:3])
68+
ver2 = entry["ver2"]
69+
short = ".".join(ver2.split(".")[:3])
5470
if short not in entry["src"]:
5571
raise SystemExit(f"{requested}: '{short}' not found in src {entry['src']}")
5672

57-
flavors = buildable_flavors(cfg, requested, entry["ver2"])
73+
# a trailing F marks the free-threaded build; it is not part of the version
74+
python_version = requested[:-1] if requested.endswith("F") else requested
75+
arch = "64F" if requested.endswith("F") else "64"
76+
77+
flavors = []
78+
for flavor in cfg["flavors"]:
79+
if str(flavor.get("WINPYARCHDET", "")) != arch:
80+
continue
81+
leg = flavor_entry(cfg, flavor, ver2, python_version)
82+
if leg is not None:
83+
flavors.append(leg)
5884
if not flavors:
5985
raise SystemExit(
60-
f"{requested}: no pylock.64-{entry['ver2'].replace('.', '_')}<flavor>"
86+
f"{requested}: no pylock.64-{ver2.replace('.', '_')}<flavor>"
6187
f"{cfg.get('release_level', '')}.toml under {cfg['cycle_dir']}; "
6288
"commit the lockfiles for this cycle before dispatching"
6389
)
6490

91+
build_location = f"WPy64-{ver2.replace('.', '')}"
6592
return {
66-
"ver2": entry["ver2"],
93+
"ver2": ver2,
94+
"python_version": python_version,
6795
"src": entry["src"],
6896
"sha": entry["sha"],
6997
"cycle_dir": cfg["cycle_dir"],
7098
"release_level": cfg.get("release_level", ""),
99+
"build_location": build_location,
100+
"destwheelhouse": f"{build_location}\\wheelhouse\\included.wheels",
71101
"pandoc_source": cfg["pandoc"]["source"],
72102
"pandoc_sha256": cfg["pandoc"]["sha256"],
73103
# consumed by the build job as strategy.matrix via fromJSON

‎.github/workflows/build_winpython_cycle.yml‎

Lines changed: 11 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ jobs:
3434
runs-on: ubuntu-latest
3535
outputs:
3636
ver2: ${{ steps.cfg.outputs.ver2 }}
37+
build_location: ${{ steps.cfg.outputs.build_location }}
38+
destwheelhouse: ${{ steps.cfg.outputs.destwheelhouse }}
3739
src: ${{ steps.cfg.outputs.src }}
3840
sha: ${{ steps.cfg.outputs.sha }}
39-
cycle_dir: ${{ steps.cfg.outputs.cycle_dir }}
40-
release_level: ${{ steps.cfg.outputs.release_level }}
4141
pandoc_source: ${{ steps.cfg.outputs.pandoc_source }}
4242
pandoc_sha256: ${{ steps.cfg.outputs.pandoc_sha256 }}
4343
matrix: ${{ steps.cfg.outputs.matrix }}
@@ -58,120 +58,40 @@ jobs:
5858
matrix: ${{ fromJSON(needs.config.outputs.matrix) }}
5959

6060
env:
61-
PYTHON_VERSIONF: ${{ inputs.python_versionf }}
6261
WINPYFLAVOR: ${{ matrix.flavor.name }}
63-
PANDOC: ${{ matrix.flavor.PANDOC }}
64-
WINPYARCHDET: ${{ matrix.flavor.WINPYARCHDET }}
65-
my_cycle: ${{ needs.config.outputs.cycle_dir }}
66-
my_release_level: ${{ needs.config.outputs.release_level }}
62+
WINPYVER: ${{ matrix.flavor.winpyver }}
63+
WINPYVER2: ${{ needs.config.outputs.ver2 }}
64+
build_location: ${{ needs.config.outputs.build_location }}
65+
destwheelhouse: ${{ needs.config.outputs.destwheelhouse }}
66+
WINPYLOCKFILE: ${{ matrix.flavor.lockfile }}
67+
WINPYLOCKFILEwhl: ${{ matrix.flavor.lockfile_wheels }}
6768

6869
steps:
6970
- name: Checkout repository
7071
uses: actions/checkout@v6
7172

72-
- name: Set static and matrix variables based on selected Python version
73-
shell: pwsh
74-
env:
75-
PYTHON_VERSIONF: ${{ env.PYTHON_VERSIONF }}
76-
WINPYARCHDET: ${{ env.WINPYARCHDET }}
77-
WINPYVER2: ${{ needs.config.outputs.ver2 }}
78-
my_cycle: ${{ env.my_cycle }}
79-
my_release_level: ${{ env.my_release_level }}
80-
FLAVOR_NAME: ${{ matrix.flavor.name }}
81-
run: |
82-
# Normalize PYTHON_VERSION by removing trailing 'F' if present
83-
$PYTHON_VERSION = $env:PYTHON_VERSIONF -replace 'F$',''
84-
Add-Content -Path $env:GITHUB_ENV -Value "PYTHON_VERSION=$PYTHON_VERSION"
85-
86-
# Detect architecture (64 or 64F)
87-
$detected_arch = if ($env:PYTHON_VERSIONF -like '*F') { '64F' } else { '64' }
88-
89-
$WINPYVER2 = $env:WINPYVER2
90-
$BUILD_LOCATION = "WPy64-" + ($WINPYVER2 -replace '[.]', "")
91-
Add-Content -Path $env:GITHUB_ENV -Value "build_location=$BUILD_LOCATION"
92-
93-
# ver2-vs-source consistency is checked in cycle_config.py
94-
95-
$WINPYREQUIREMENTS = ''
96-
$WINPYREQUIREMENTSwhl = ''
97-
98-
# Generate requirement files expected names dynamically
99-
$V_TAG = $env:WINPYVER2 -replace '\.', '_'
100-
$testreq = "$($env:my_cycle)/requir.64-$($V_TAG)$($env:FLAVOR_NAME)$($env:my_release_level).txt"
101-
$testwhl = "$($env:my_cycle)/requir.64-$($V_TAG)$($env:FLAVOR_NAME)$($env:my_release_level)_wheels.txt"
102-
103-
Write-Host "Checking for requirements files: $testreq and $testwhl (expected arch $detected_arch)"
104-
105-
if ($env:WINPYARCHDET -eq $detected_arch -and (Test-Path $testreq)) {
106-
$WINPYREQUIREMENTS = $testreq
107-
Write-Host "Found $WINPYREQUIREMENTS"
108-
if (Test-Path $testwhl) {
109-
$WINPYREQUIREMENTSwhl = $testwhl
110-
Write-Host "Found also $WINPYREQUIREMENTSwhl"
111-
}
112-
}
113-
114-
Add-Content -Path $env:GITHUB_ENV -Value "WINPYREQUIREMENTS=$WINPYREQUIREMENTS"
115-
Add-Content -Path $env:GITHUB_ENV -Value "WINPYREQUIREMENTSwhl=$WINPYREQUIREMENTSwhl"
116-
117-
$WINPYLOCKFILE = ''
118-
$WINPYLOCKFILEwhl = ''
119-
120-
# Generate pylock files expected names dynamically
121-
$testreq = "$($env:my_cycle)/pylock.64-$($V_TAG)$($env:FLAVOR_NAME)$($env:my_release_level).toml"
122-
$testwhl = "$($env:my_cycle)/pylock.64-$($V_TAG)$($env:FLAVOR_NAME)$($env:my_release_level)_wheels.toml"
123-
124-
Write-Host "Checking for pylock files: $testreq and $testwhl (expected arch $detected_arch)"
125-
126-
if ($env:WINPYARCHDET -eq $detected_arch -and (Test-Path $testreq)) {
127-
$WINPYLOCKFILE = $testreq
128-
Write-Host "Found $WINPYLOCKFILE"
129-
if (Test-Path $testwhl) {
130-
$WINPYLOCKFILEwhl = $testwhl
131-
Write-Host "Found also $WINPYLOCKFILEwhl"
132-
}
133-
}
134-
135-
Add-Content -Path $env:GITHUB_ENV -Value "WINPYLOCKFILE=$WINPYLOCKFILE"
136-
Add-Content -Path $env:GITHUB_ENV -Value "WINPYLOCKFILEwhl=$WINPYLOCKFILEwhl"
137-
138-
$ARTIFACT_NAME = "publish_${PYTHON_VERSION}$($env:FLAVOR_NAME)"
139-
Add-Content -Path $env:GITHUB_ENV -Value "ARTIFACT_NAME=$ARTIFACT_NAME"
140-
141-
$destwheelhouse = "$BUILD_LOCATION\wheelhouse\included.wheels"
142-
Add-Content -Path $env:GITHUB_ENV -Value "destwheelhouse=$destwheelhouse"
143-
144-
$WINPYVER = "${WINPYVER2}$($env:FLAVOR_NAME)$($env:my_release_level)"
145-
Add-Content -Path $env:GITHUB_ENV -Value "WINPYVER=$WINPYVER"
146-
147-
# Store WINPYVER2 in env for later steps
148-
Add-Content -Path $env:GITHUB_ENV -Value "WINPYVER2=$WINPYVER2"
149-
15073
- name: Download, verify and extract python standalone
151-
if: env.WINPYLOCKFILE != ''
15274
uses: ./.github/actions/python-setup
15375
with:
15476
python_source: ${{ needs.config.outputs.src }}
15577
python_sha256: ${{ needs.config.outputs.sha }}
15678
build_location: ${{ env.build_location }}
15779

15880
- name: Download, checking hash and integrating pandoc binary
159-
if: env.WINPYLOCKFILE != '' && env.PANDOC == '1'
81+
if: matrix.flavor.PANDOC == '1'
16082
uses: ./.github/actions/pandoc-setup
16183
with:
16284
pandoc_source: ${{ needs.config.outputs.pandoc_source }}
16385
pandoc_sha256: ${{ needs.config.outputs.pandoc_sha256 }}
16486
build_location: ${{ env.build_location }}
16587

16688
- name: Upgrade pip and patch launchers
167-
if: env.WINPYLOCKFILE != ''
16889
shell: pwsh
16990
run: |
17091
& "$env:build_location\python\python.exe" -m pip install --upgrade --force-reinstall pip --no-warn-script-location
17192
& "$env:build_location\python\python.exe" -c "from wppm import wppm;dist=wppm.Distribution();dist.patch_standard_packages('pip', to_movable=True)"
17293
17394
- name: Download all requirements
174-
if: ${{ env.WINPYLOCKFILE != '' }}
17595
shell: pwsh
17696
run: |
17797
$py = "$env:build_location\python\python.exe"
@@ -181,13 +101,11 @@ jobs:
181101
}
182102
183103
- name: Install lockfile
184-
if: env.WINPYLOCKFILE != ''
185104
shell: pwsh
186105
run: |
187106
& "$env:build_location\python\python.exe" -m pip install --no-deps --require-hashes -r $env:WINPYLOCKFILE --no-warn-script-location
188107
189108
- name: Generate Assets and Hashes
190-
if: env.WINPYLOCKFILE != ''
191109
uses: ./.github/actions/publish-winpython
192110
with:
193111
build_location: ${{ env.build_location }}
@@ -196,15 +114,14 @@ jobs:
196114
winpy_ver: ${{ env.WINPYVER }}
197115
winpy_ver2: ${{ env.WINPYVER2 }}
198116
dotwheelhouse: ${{ env.dotwheelhouse }}
199-
winpy_requirements_whl: ${{ env.WINPYREQUIREMENTSwhl }}
117+
winpy_requirements_whl: ${{ matrix.flavor.requirements_wheels }}
200118
format_zip: ${{ matrix.flavor.formats.zip }}
201119
format_7z: ${{ matrix.flavor.formats['7z'] }}
202120
format_exe: ${{ matrix.flavor.formats.exe }}
203121

204122
- name: Upload artifacts
205-
if: env.WINPYLOCKFILE != ''
206123
uses: actions/upload-artifact@v6
207124
with:
208-
name: ${{ env.ARTIFACT_NAME }}
125+
name: ${{ matrix.flavor.artifact_name }}
209126
path: publish_output
210127
retention-days: 66 # keeps artifact for 66 days

0 commit comments

Comments
 (0)