-
Notifications
You must be signed in to change notification settings - Fork 0
159 lines (140 loc) · 5.65 KB
/
Copy pathpublish-python.yml
File metadata and controls
159 lines (140 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
name: publish-python
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
release_tag:
description: "Existing Git tag to validate, for example v1.2.4. Manual runs never publish to PyPI."
required: true
type: string
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
# Pin pip instead of taking whatever released most recently. Every job used to
# run `pip install --upgrade pip`, so each run resolved dependencies with a
# different, unannounced resolver: CI was non-reproducible by construction,
# and a pip release could break the build with no change in this repository.
#
# This does NOT prevent transient index failures, and should not be mistaken
# for a fix for one. A case is on record: the pull_request run for PR #14
# failed with `no matching distributions available for your environment:
# mdurl` while the identical commit had passed 15 minutes earlier on push,
# passed on two other platforms in that same run, and passed again on re-run
# with no code change. mdurl is a pure-Python py3-none-any wheel, so nothing
# about the environment made it uninstallable -- that was the index, not the
# resolver. The right response there is a re-run. The right response to an
# unpinned toolchain is this pin.
#
# Bump deliberately, as its own commit, so a resolver change lands where it
# can be attributed instead of appearing inside an unrelated PR.
PIP_VERSION: "26.2.1"
jobs:
build:
name: build-sdist-wheel
runs-on: ubuntu-latest
env:
RELEASE_TAG: ${{ github.event.inputs.release_tag || github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.release_tag || github.ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Validate committed release metadata
run: python scripts/release.py --check-tag "${RELEASE_TAG}"
- name: Build package
run: |
python -m pip install "pip==${{ env.PIP_VERSION }}" build twine
rm -rf dist build *.egg-info
python -m build
python -m twine check dist/*
- name: Verify built artifact versions
run: |
python - <<'PY'
from pathlib import Path
import os
import tomllib
tag = os.environ["RELEASE_TAG"].strip()
if not tag.startswith("v"):
raise SystemExit(f"Release tag must start with 'v': {tag}")
version = tag[1:]
pyproject_version = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["version"]
if pyproject_version != version:
raise SystemExit(
f"pyproject.toml version {pyproject_version} does not match release tag version {version}"
)
dist = sorted(Path("dist").glob("*"))
if not dist:
raise SystemExit("No build artifacts found in dist/")
names = [path.name for path in dist]
expected = {
f"vcf_rdfizer-{version}.tar.gz",
f"vcf_rdfizer-{version}-py3-none-any.whl",
}
missing = sorted(expected.difference(names))
if missing:
raise SystemExit(
f"Built artifacts do not match project version {version}; missing: {', '.join(missing)}"
)
unexpected = sorted(
name for name in names if name.startswith("vcf_rdfizer-") and f"vcf_rdfizer-{version}" not in name
)
if unexpected:
raise SystemExit(
f"Found stale artifacts in dist/: {', '.join(unexpected)}"
)
print("Verified build artifacts:")
for name in names:
print(f" {name}")
PY
- name: Smoke-test installed wheel
run: |
python -m venv /tmp/vcf-rdfizer-wheel-test
/tmp/vcf-rdfizer-wheel-test/bin/python -m pip install --no-deps dist/*.whl
(
cd /tmp
/tmp/vcf-rdfizer-wheel-test/bin/vcf-rdfizer --help
/tmp/vcf-rdfizer-wheel-test/bin/python - <<'PY'
import importlib.resources
from pathlib import Path
import vcf_rdfizer
rules = importlib.resources.files("vcf_rdfizer_data").joinpath("rules/default_rules.ttl")
if not rules.is_file():
raise SystemExit("Packaged default rules are missing")
if not vcf_rdfizer.resolve_default_rules_path(Path(vcf_rdfizer.__file__).resolve().parent).is_file():
raise SystemExit("Installed wrapper cannot resolve packaged default rules")
PY
)
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: python-dist
path: dist/*
publish:
name: publish-to-pypi
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
environment:
name: pypi
permissions:
id-token: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: python-dist
path: dist
# PyPI reserves a version permanently once it is uploaded, so re-pushing a
# release tag -- to correct a Dockerfile, a workflow, or anything else
# outside the Python artifact -- used to fail this job on the duplicate.
# Skipping what is already published makes a tag re-push idempotent: the
# Docker and release jobs re-run, and PyPI is left exactly as it was.
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true