Skip to content

Commit 9bda95d

Browse files
committed
build: artifact@4, with required immutability changes
https://github.com/actions/upload-artifact/blob/main/docs/MIGRATION.md with discussion here: actions/upload-artifact#472
1 parent 390fa6c commit 9bda95d

File tree

4 files changed

+91
-37
lines changed

4 files changed

+91
-37
lines changed

.github/workflows/coverage.yml

+11-7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ jobs:
3131
coverage:
3232
name: "${{ matrix.python-version }} on ${{ matrix.os }}"
3333
runs-on: "${{ matrix.os }}-latest"
34+
env:
35+
MATRIX_ID: "${{ matrix.python-version }}.${{ matrix.os }}"
3436

3537
strategy:
3638
matrix:
@@ -76,6 +78,7 @@ jobs:
7678

7779
- name: "Install dependencies"
7880
run: |
81+
echo matrix id: $MATRIX_ID
7982
set -xe
8083
python -VV
8184
python -m site
@@ -94,12 +97,12 @@ jobs:
9497
COVERAGE_RCFILE: "metacov.ini"
9598
run: |
9699
python -m coverage combine
97-
mv .metacov .metacov.${{ matrix.python-version }}.${{ matrix.os }}
100+
mv .metacov .metacov.$MATRIX_ID
98101
99102
- name: "Upload coverage data"
100-
uses: actions/upload-artifact@v3
103+
uses: actions/upload-artifact@v4
101104
with:
102-
name: metacov
105+
name: metacov-${{ env.MATRIX_ID }}
103106
path: .metacov.*
104107

105108
combine:
@@ -131,9 +134,10 @@ jobs:
131134
python igor.py zip_mods
132135
133136
- name: "Download coverage data"
134-
uses: actions/download-artifact@v3
137+
uses: actions/download-artifact@v4
135138
with:
136-
name: metacov
139+
pattern: metacov-*
140+
merge-multiple: true
137141

138142
- name: "Combine and report"
139143
id: combine
@@ -144,7 +148,7 @@ jobs:
144148
python igor.py combine_html
145149
146150
- name: "Upload HTML report"
147-
uses: actions/upload-artifact@v3
151+
uses: actions/upload-artifact@v4
148152
with:
149153
name: html_report
150154
path: htmlcov
@@ -193,7 +197,7 @@ jobs:
193197
194198
- name: "Download coverage HTML report"
195199
if: ${{ github.ref == 'refs/heads/master' }}
196-
uses: actions/download-artifact@v3
200+
uses: actions/download-artifact@v4
197201
with:
198202
name: html_report
199203
path: reports_repo/${{ env.report_dir }}

.github/workflows/kit.yml

+12-9
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ jobs:
4949
wheels:
5050
name: "${{ matrix.py }} ${{ matrix.os }} ${{ matrix.arch }} wheels"
5151
runs-on: ${{ matrix.os }}-latest
52+
env:
53+
MATRIX_ID: "${{ matrix.py }}-${{ matrix.os }}-${{ matrix.arch }}"
5254
strategy:
5355
matrix:
5456
include:
@@ -173,9 +175,9 @@ jobs:
173175
ls -al wheelhouse/
174176
175177
- name: "Upload wheels"
176-
uses: actions/upload-artifact@v3
178+
uses: actions/upload-artifact@v4
177179
with:
178-
name: dist
180+
name: dist-${{ env.MATRIX_ID }}
179181
path: wheelhouse/*.whl
180182
retention-days: 7
181183

@@ -207,9 +209,9 @@ jobs:
207209
ls -al dist/
208210
209211
- name: "Upload sdist"
210-
uses: actions/upload-artifact@v3
212+
uses: actions/upload-artifact@v4
211213
with:
212-
name: dist
214+
name: dist-sdist
213215
path: dist/*.tar.gz
214216
retention-days: 7
215217

@@ -245,9 +247,9 @@ jobs:
245247
ls -al dist/
246248
247249
- name: "Upload wheels"
248-
uses: actions/upload-artifact@v3
250+
uses: actions/upload-artifact@v4
249251
with:
250-
name: dist
252+
name: dist-pypy
251253
path: dist/*.whl
252254
retention-days: 7
253255

@@ -264,9 +266,10 @@ jobs:
264266
id-token: write
265267
steps:
266268
- name: "Download artifacts"
267-
uses: actions/download-artifact@v3
269+
uses: actions/download-artifact@v4
268270
with:
269-
name: dist
271+
pattern: dist-*
272+
merge-multiple: true
270273

271274
- name: "Sign artifacts"
272275
uses: sigstore/[email protected]
@@ -278,7 +281,7 @@ jobs:
278281
ls -alR
279282
280283
- name: "Upload signatures"
281-
uses: actions/upload-artifact@v3
284+
uses: actions/upload-artifact@v4
282285
with:
283286
name: signatures
284287
path: |

Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,11 @@ build_kits: ## Trigger GitHub to build kits
213213
python ci/trigger_build_kits.py $(REPO_OWNER)
214214

215215
download_kits: ## Download the built kits from GitHub.
216-
python ci/download_gha_artifacts.py $(REPO_OWNER)
216+
python ci/download_gha_artifacts.py $(REPO_OWNER) 'dist-*' dist
217217

218218
check_kits: ## Check that dist/* are well-formed.
219219
python -m twine check dist/*
220+
@echo $$(ls -1 dist | wc -l) distribution kits
220221

221222
tag: ## Make a git tag with the version number.
222223
git tag -a -m "Version $$(python setup.py --version)" $$(python setup.py --version)

ci/download_gha_artifacts.py

+66-20
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
"""Use the GitHub API to download built artifacts."""
55

6+
import collections
67
import datetime
7-
import json
8+
import fnmatch
9+
import operator
810
import os
911
import os.path
1012
import sys
@@ -13,6 +15,7 @@
1315

1416
import requests
1517

18+
1619
def download_url(url, filename):
1720
"""Download a file from `url` to `filename`."""
1821
response = requests.get(url, stream=True)
@@ -23,6 +26,7 @@ def download_url(url, filename):
2326
else:
2427
raise RuntimeError(f"Fetching {url} produced: status={response.status_code}")
2528

29+
2630
def unpack_zipfile(filename):
2731
"""Unpack a zipfile, using the names in the zip."""
2832
with open(filename, "rb") as fzip:
@@ -31,8 +35,10 @@ def unpack_zipfile(filename):
3135
print(f" extracting {name}")
3236
z.extract(name)
3337

38+
3439
def utc2local(timestring):
35-
"""Convert a UTC time into local time in a more readable form.
40+
"""
41+
Convert a UTC time into local time in a more readable form.
3642
3743
For example: '20201208T122900Z' to '2020-12-08 07:29:00'.
3844
@@ -44,25 +50,65 @@ def utc2local(timestring):
4450
local = utc + offset
4551
return local.strftime("%Y-%m-%d %H:%M:%S")
4652

47-
dest = "dist"
48-
repo_owner = sys.argv[1]
49-
temp_zip = "artifacts.zip"
5053

51-
os.makedirs(dest, exist_ok=True)
52-
os.chdir(dest)
54+
def all_items(url, key):
55+
"""
56+
Get all items from a paginated GitHub URL.
5357
54-
r = requests.get(f"https://api.github.com/repos/{repo_owner}/actions/artifacts")
55-
if r.status_code == 200:
56-
dists = [a for a in r.json()["artifacts"] if a["name"] == "dist"]
57-
if not dists:
58-
print("No recent dists!")
59-
else:
60-
latest = max(dists, key=lambda a: a["created_at"])
61-
print(f"Artifacts created at {utc2local(latest['created_at'])}")
62-
download_url(latest["archive_download_url"], temp_zip)
58+
`key` is the key in the top-level returned object that has a list of items.
59+
60+
"""
61+
url += ("&" if "?" in url else "?") + "per_page=100"
62+
while url:
63+
response = requests.get(url)
64+
response.raise_for_status()
65+
data = response.json()
66+
if isinstance(data, dict) and (msg := data.get("message")):
67+
raise RuntimeError(f"URL {url!r} failed: {msg}")
68+
yield from data.get(key, ())
69+
try:
70+
url = response.links.get("next").get("url")
71+
except AttributeError:
72+
url = None
73+
74+
75+
def main(owner_repo, artifact_pattern, dest_dir):
76+
"""
77+
Download and unzip the latest artifacts matching a pattern.
78+
79+
`owner_repo` is a GitHub pair for the repo, like "nedbat/coveragepy".
80+
`artifact_pattern` is a filename glob for the artifact name.
81+
`dest_dir` is the directory to unpack them into.
82+
83+
"""
84+
# Get all artifacts matching the pattern, grouped by name.
85+
url = f"https://api.github.com/repos/{owner_repo}/actions/artifacts"
86+
artifacts_by_name = collections.defaultdict(list)
87+
for artifact in all_items(url, "artifacts"):
88+
name = artifact["name"]
89+
if not fnmatch.fnmatch(name, artifact_pattern):
90+
continue
91+
artifacts_by_name[name].append(artifact)
92+
93+
os.makedirs(dest_dir, exist_ok=True)
94+
os.chdir(dest_dir)
95+
temp_zip = "artifacts.zip"
96+
97+
# Download the latest of each name.
98+
# I'd like to use created_at, because it seems like the better value to use,
99+
# but it is in the wrong time zone, and updated_at is the same but correct.
100+
# Bug report here: https://github.com/actions/upload-artifact/issues/488.
101+
for name, artifacts in artifacts_by_name.items():
102+
artifact = max(artifacts, key=operator.itemgetter("updated_at"))
103+
print(
104+
f"Downloading {artifact['name']}, "
105+
+ f"size: {artifact['size_in_bytes']}, "
106+
+ f"created: {utc2local(artifact['updated_at'])}"
107+
)
108+
download_url(artifact["archive_download_url"], temp_zip)
63109
unpack_zipfile(temp_zip)
64110
os.remove(temp_zip)
65-
else:
66-
print(f"Fetching artifacts returned status {r.status_code}:")
67-
print(json.dumps(r.json(), indent=4))
68-
sys.exit(1)
111+
112+
113+
if __name__ == "__main__":
114+
sys.exit(main(*sys.argv[1:]))

0 commit comments

Comments
 (0)