@@ -62,11 +62,11 @@ jobs:
6262 /tmp/test_venv/bin/python -c "import dmPython; print('dmPython version:', dmPython.version)"
6363
6464 - name : Build extension for optional integration tests
65- if : ${{ secrets.DM_TEST_HOST != '' && secrets.DM_TEST_PORT != '' && secrets.DM_TEST_USER != '' && secrets.DM_TEST_PASSWORD != '' }}
65+ if : ${{ matrix.python-version == '3.10' && secrets.DM_TEST_HOST != '' && secrets.DM_TEST_PORT != '' && secrets.DM_TEST_USER != '' && secrets.DM_TEST_PASSWORD != '' }}
6666 run : DMPYTHON_SKIP_GO_BUILD=1 python setup.py build_ext --inplace
6767
6868 - name : Run optional DM integration tests
69- if : ${{ secrets.DM_TEST_HOST != '' && secrets.DM_TEST_PORT != '' && secrets.DM_TEST_USER != '' && secrets.DM_TEST_PASSWORD != '' }}
69+ if : ${{ matrix.python-version == '3.10' && secrets.DM_TEST_HOST != '' && secrets.DM_TEST_PORT != '' && secrets.DM_TEST_USER != '' && secrets.DM_TEST_PASSWORD != '' }}
7070 env :
7171 DYLD_LIBRARY_PATH : ${{ github.workspace }}/dpi_bridge
7272 DM_TEST_HOST : ${{ secrets.DM_TEST_HOST }}
@@ -83,15 +83,16 @@ jobs:
8383 python -m pytest -q -m requires_dm tests --cov=. --cov-report=term-missing --cov-report=xml:coverage.xml
8484
8585 - name : Upload optional integration coverage
86- if : ${{ secrets.DM_TEST_HOST != '' && secrets.DM_TEST_PORT != '' && secrets.DM_TEST_USER != '' && secrets.DM_TEST_PASSWORD != '' }}
86+ if : ${{ matrix.python-version == '3.10' && secrets.DM_TEST_HOST != '' && secrets.DM_TEST_PORT != '' && secrets.DM_TEST_USER != '' && secrets.DM_TEST_PASSWORD != '' }}
8787 uses : actions/upload-artifact@v4
8888 with :
89- name : coverage-wheel-py${{ matrix.python-version }}
89+ name : coverage-wheel-baseline- py${{ matrix.python-version }}
9090 path : coverage.xml
9191
9292 - name : Skip optional integration tests (missing DM secrets)
93- if : ${{ secrets.DM_TEST_HOST == '' || secrets.DM_TEST_PORT == '' || secrets.DM_TEST_USER == '' || secrets.DM_TEST_PASSWORD == '' }}
94- run : echo "Skipping requires_dm tests: DM_TEST_HOST/PORT/USER/PASSWORD secrets not fully configured."
93+ if : ${{ matrix.python-version == '3.10' && (secrets.DM_TEST_HOST == '' || secrets.DM_TEST_PORT == '' || secrets.DM_TEST_USER == '' || secrets.DM_TEST_PASSWORD == '') }}
94+ run : |
95+ echo "::notice::SKIP_DM_INTEGRATION: DM_TEST_HOST/PORT/USER/PASSWORD secrets are not fully configured."
9596
9697 - uses : actions/upload-artifact@v4
9798 with :
@@ -114,15 +115,103 @@ jobs:
114115 pattern : wheel-*
115116 merge-multiple : true
116117
118+ - name : Validate wheel set and generate release metadata
119+ env :
120+ TAG_NAME : ${{ github.ref_name }}
121+ run : |
122+ python - <<'PY'
123+ import glob
124+ import json
125+ import os
126+ import re
127+ import subprocess
128+ import sys
129+
130+ wheels = sorted(glob.glob("dist_fixed/*.whl"))
131+ if len(wheels) != 5:
132+ print(f"Expected 5 wheels, got {len(wheels)}")
133+ print("\n".join(wheels))
134+ sys.exit(1)
135+
136+ expected_tags = {"cp39-cp39", "cp310-cp310", "cp311-cp311", "cp312-cp312", "cp313-cp313"}
137+ found_tags = set()
138+ for wheel in wheels:
139+ name = os.path.basename(wheel)
140+ if "arm64.whl" not in name:
141+ print(f"Non-arm64 wheel found: {name}")
142+ sys.exit(1)
143+ match = re.search(r"-(cp\d{2,3}-cp\d{2,3})-macosx_.*_arm64\.whl$", name)
144+ if not match:
145+ print(f"Unrecognized wheel naming pattern: {name}")
146+ sys.exit(1)
147+ found_tags.add(match.group(1))
148+ if found_tags != expected_tags:
149+ print(f"Wheel tag mismatch. expected={sorted(expected_tags)} found={sorted(found_tags)}")
150+ sys.exit(1)
151+
152+ with open("dist_fixed/build-metadata.json", "w", encoding="utf-8") as f:
153+ json.dump(
154+ {
155+ "tag": os.environ["TAG_NAME"],
156+ "commit": os.environ.get("GITHUB_SHA", ""),
157+ "workflow": os.environ.get("GITHUB_WORKFLOW", ""),
158+ "run_id": os.environ.get("GITHUB_RUN_ID", ""),
159+ "run_attempt": os.environ.get("GITHUB_RUN_ATTEMPT", ""),
160+ "wheels": [os.path.basename(w) for w in wheels],
161+ },
162+ f,
163+ ensure_ascii=False,
164+ indent=2,
165+ )
166+
167+ subprocess.check_call("shasum -a 256 dist_fixed/*.whl > dist_fixed/checksums.txt", shell=True)
168+ PY
169+
117170 - name : Upsert GitHub Release
118171 env :
119172 GH_TOKEN : ${{ github.token }}
120173 TAG_NAME : ${{ github.ref_name }}
121174 run : |
122175 if gh release view "$TAG_NAME" >/dev/null 2>&1; then
123176 echo "Release $TAG_NAME already exists, uploading artifacts with --clobber"
124- gh release upload "$TAG_NAME" dist_fixed/*.whl --clobber
177+ gh release upload "$TAG_NAME" dist_fixed/*.whl dist_fixed/checksums.txt dist_fixed/build-metadata.json --clobber
125178 else
126179 echo "Creating release $TAG_NAME"
127- gh release create "$TAG_NAME" --generate-notes dist_fixed/*.whl
180+ gh release create "$TAG_NAME" --generate-notes dist_fixed/*.whl dist_fixed/checksums.txt dist_fixed/build-metadata.json
128181 fi
182+
183+ - name : Verify release assets completeness
184+ env :
185+ GH_TOKEN : ${{ github.token }}
186+ TAG_NAME : ${{ github.ref_name }}
187+ run : |
188+ python - <<'PY'
189+ import json
190+ import os
191+ import re
192+ import subprocess
193+ import sys
194+
195+ tag = os.environ["TAG_NAME"]
196+ raw = subprocess.check_output(
197+ ["gh", "release", "view", tag, "--json", "assets"],
198+ text=True,
199+ )
200+ assets = [a["name"] for a in json.loads(raw)["assets"]]
201+ expected_tags = {"cp39-cp39", "cp310-cp310", "cp311-cp311", "cp312-cp312", "cp313-cp313"}
202+ found_tags = set()
203+ for name in assets:
204+ m = re.search(r"-(cp\d{2,3}-cp\d{2,3})-macosx_.*_arm64\.whl$", name)
205+ if m:
206+ found_tags.add(m.group(1))
207+ missing_tags = sorted(expected_tags - found_tags)
208+ required_files = {"checksums.txt", "build-metadata.json"}
209+ missing_files = sorted(f for f in required_files if f not in assets)
210+ if missing_tags or missing_files:
211+ print("Release assets are incomplete.")
212+ print(f"missing wheel tags: {missing_tags}")
213+ print(f"missing files: {missing_files}")
214+ print(f"assets: {assets}")
215+ sys.exit(1)
216+ print("Release assets verified:", assets)
217+ PY
0 commit comments