Repository navigation
236 lines (209 loc) · 9.75 KB
/
Copy pathrelease.yml
File metadata and controls
236 lines (209 loc) · 9.75 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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# Release automation: build the source distribution, publish it to PyPI and
# create a GitHub release with the standalone binaries.
#
# This is called by ci.yml when a tag is pushed, and it deliberately runs as
# part of that same workflow run: that is where the binaries for the tag are
# built, and artifacts can only be downloaded within the run that created them.
#
# The GitHub release is created as a *draft* on purpose:
# - the release notes want a human,
# - the detached GPG signature of the sdist can only be made locally
# (scripts/sdist-sign), so it has to be added by hand,
# - and the binaries should be tried out before the release becomes visible.
# Publishing the draft is a single click in the GitHub UI.
#
# Every release asset has a build provenance attestation, and the sigstore bundle
# of each attestation is attached to the release as <asset>.sigstore.jsonl, for
# offline verification. The sdist is attested here, the binaries by the jobs in
# ci.yml that build them - those hand their bundles over in the artifact that
# carries the binaries.
#
# The upload to PyPI is *not* done here, but by the "pypi" job in ci.yml: PyPI
# trusted publishing can not be used from a reusable workflow, see
# https://docs.pypi.org/trusted-publishers/troubleshooting/ - so that job has to
# live in a workflow that is triggered by an event. This job hands the sdist
# over to it as a workflow artifact.
name: Release
on:
workflow_call:
permissions:
contents: read
env:
# The standalone binaries expected for a release: for every platform the
# single-file binary and, as a .tgz, the single-directory variant. See the
# "binary" entries of the native_tests and oldglibc_binary matrices, the
# "artifact_prefix" entries of the vm_tests matrix and the windows_tests job
# in ci.yml - keep in sync.
EXPECTED_ASSETS: >-
borg-linux-glibc243-x86_64-gh
borg-linux-glibc243-x86_64-gh.tgz
borg-linux-glibc243-arm64-gh
borg-linux-glibc243-arm64-gh.tgz
borg-linux-glibc239-x86_64-gh
borg-linux-glibc239-x86_64-gh.tgz
borg-linux-glibc239-arm64-gh
borg-linux-glibc239-arm64-gh.tgz
borg-macos-15-arm64-gh
borg-macos-15-arm64-gh.tgz
borg-macos-15-x86_64-gh
borg-macos-15-x86_64-gh.tgz
borg-freebsd-15-x86_64-gh
borg-freebsd-15-x86_64-gh.tgz
borg-windows-x86_64-gh.exe
borg-windows-x86_64-gh.tgz
jobs:
github_release:
name: Draft the GitHub release
runs-on: ubuntu-26.04
timeout-minutes: 30
permissions:
contents: write # to create the release
id-token: write # to attest the sdist
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Just fetching one commit is not enough for setuptools-scm, so we fetch all.
fetch-depth: 0
fetch-tags: true
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.13'
- name: Install Linux packages
run: |
sudo apt-get update
sudo apt-get install -y pkg-config build-essential
sudo apt-get install -y libssl-dev libacl1-dev liblz4-dev
- name: Build the sdist
run: |
set -euxo pipefail
python -m pip install --upgrade pip build twine
# only a sdist: we do not publish wheels, they would be platform specific.
python -m build --sdist
twine check dist/*
ls -l dist/
- name: Check that the sdist has the version of the tag
# If the checkout is modified, setuptools-scm does not use the tag as the
# version, but silently guesses the next one and appends .devN, see
# #10199. Such a release must not be uploaded to PyPI.
env:
TAG: ${{ github.ref_name }}
run: |
set -euxo pipefail
python - "$TAG" dist/borgbackup-*.tar.gz <<'EOF'
import sys
from packaging.utils import parse_sdist_filename
from packaging.version import Version
tag, sdist = sys.argv[1], sys.argv[2]
_, version = parse_sdist_filename(sdist.rpartition("/")[2])
if version != Version(tag):
raise SystemExit(f"sdist version {version} is not the tag version {tag} - modified checkout?")
print(f"sdist version {version} is the tag version.")
EOF
- name: Check that the sdist is complete and installable
# A release that cannot be installed from PyPI is the worst kind of
# release, and nothing else in CI ever installs borg from a sdist or
# without the development requirements.
run: |
set -euxo pipefail
python -m venv "$RUNNER_TEMP/venv-sdist"
"$RUNNER_TEMP/venv-sdist/bin/pip" install --upgrade pip
"$RUNNER_TEMP/venv-sdist/bin/pip" install dist/borgbackup-*.tar.gz
"$RUNNER_TEMP/venv-sdist/bin/borg" --version
"$RUNNER_TEMP/venv-sdist/bin/borg" --help > /dev/null
- name: Attest the sdist provenance
id: attest-sdist
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: 'dist/*.tar.gz'
- name: Name the sigstore bundle after the sdist
# The attestation is also stored in the GitHub attestations API, but
# fetching it from there needs a recent gh and a GitHub token (see
# #10187) - so attach the sigstore bundle to the release as
# <sdist>.sigstore.jsonl, which allows offline/token-less verification
# with generic sigstore tooling (cosign, gh attestation verify --bundle).
env:
BUNDLE_PATH: ${{ steps.attest-sdist.outputs.bundle-path }}
run: |
set -euxo pipefail
sdist=$(basename dist/borgbackup-*.tar.gz)
cp "$BUNDLE_PATH" "dist/$sdist.sigstore.jsonl"
- name: Download the binaries built for this tag
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
path: assets
# the binary artifacts are all named like the binaries they contain
pattern: 'borg-*-gh'
merge-multiple: true
- name: Check that no binary is missing
run: |
set -uo pipefail
ls -l assets/ || true
missing=""
for asset in $EXPECTED_ASSETS; do
test -f "assets/$asset" || missing="$missing $asset"
done
if [ -n "$missing" ]; then
# not an error: vm_tests is continue-on-error, so e.g. a flaky FreeBSD
# VM should not stop the release - but do not lose it silently either.
echo "::warning::binaries missing from this release:$missing"
fi
- name: Create the draft release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
set -euxo pipefail
# 2.0.0b23 and friends are pre-releases, 2.1.0 is not.
prerelease=""
case "$TAG" in *a*|*b*|*rc*) prerelease="--prerelease" ;; esac
cat > release-notes.md <<EOF
See the [changelog](https://github.com/borgbackup/borg/blob/$TAG/docs/changes.rst) for what changed in this release.
### Installation
\`pip install borgbackup==$TAG\`, or use one of the standalone binaries below - they
contain everything they need and just have to be made executable.
For each platform there is a single-file binary (\`borg-*-gh\`, \`.exe\` on Windows)
and, as a \`.tgz\`, the same thing as a directory (\`borg-dir/borg.exe\`), which
starts up faster.
The Linux binaries come in two flavours: \`glibc243\` (built on Ubuntu 26.04) and
\`glibc239\` (built on Ubuntu 24.04) for systems with an older glibc or with a CPU
below x86-64-v3, on which the \`glibc243\` x86_64 binary does not run. Your system
needs at least the glibc version in the name.
The macOS binaries are built **without** FUSE support, so \`borg mount\` does
not work with them; install via \`pip\` instead if you need FUSE support.
All release assets have a [build provenance attestation](https://github.com/borgbackup/borg/attestations),
verifiable with \`gh attestation verify --owner borgbackup <file>\`.
Every attestation is additionally attached as a [sigstore](https://www.sigstore.dev/)
bundle \`<asset>.sigstore.jsonl\`, so it can be verified offline and without a
GitHub token, e.g. with cosign:
\`\`\`
cosign verify-blob borgbackup-$TAG.tar.gz \\
--bundle borgbackup-$TAG.tar.gz.sigstore.jsonl \\
--certificate-identity https://github.com/borgbackup/borg/.github/workflows/release.yml@refs/tags/$TAG \\
--certificate-oidc-issuer https://token.actions.githubusercontent.com
\`\`\`
The binaries are built by \`ci.yml\`, not by \`release.yml\`, so their
\`--certificate-identity\` is
\`https://github.com/borgbackup/borg/.github/workflows/ci.yml@refs/tags/$TAG\`.
EOF
mkdir -p assets # there may not have been any artifact to download
if gh release view "$TAG" > /dev/null 2>&1; then
# a re-run of this job: keep the (possibly already edited) release and
# just replace its assets.
gh release upload "$TAG" --clobber dist/*.tar.gz dist/*.sigstore.jsonl $(find assets -type f | sort)
else
gh release create "$TAG" \
--draft $prerelease \
--title "borg $TAG" \
--notes-file release-notes.md \
dist/*.tar.gz dist/*.sigstore.jsonl $(find assets -type f | sort)
fi
gh release view "$TAG" --json isDraft,isPrerelease,assets
- name: Keep the sdist for the PyPI upload
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: sdist
path: dist/*.tar.gz
if-no-files-found: error