]> jfr.im git - yt-dlp.git/blame - .github/workflows/release.yml
[build] Make `secretstorage` an optional dependency (#8585)
[yt-dlp.git] / .github / workflows / release.yml
CommitLineData
29cb20bd 1name: Release
c4efa0ae 2on:
1d03633c 3 workflow_call:
c4efa0ae 4 inputs:
1d03633c 5 prerelease:
6 required: false
7 default: true
8 type: boolean
9 source:
10 required: false
11 default: ''
12 type: string
13 target:
14 required: false
15 default: ''
16 type: string
c4efa0ae 17 version:
c4efa0ae 18 required: false
19 default: ''
20 type: string
1d03633c 21 workflow_dispatch:
22 inputs:
23 source:
24 description: |
25 SOURCE of this release's updates:
26 channel, repo, tag, or channel/repo@tag
27 (default: <current_repo>)
28 required: false
29 default: ''
30 type: string
31 target:
32 description: |
33 TARGET to publish this release to:
34 channel, tag, or channel@tag
35 (default: <source> if writable else <current_repo>[@source_tag])
36 required: false
37 default: ''
38 type: string
39 version:
40 description: |
41 VERSION: yyyy.mm.dd[.rev] or rev
42 (default: auto-generated)
c4efa0ae 43 required: false
44 default: ''
45 type: string
46 prerelease:
47 description: Pre-release
48 default: false
49 type: boolean
50
29cb20bd
SS
51permissions:
52 contents: read
53
54jobs:
55 prepare:
56 permissions:
57 contents: write
58 runs-on: ubuntu-latest
59 outputs:
1d03633c 60 channel: ${{ steps.setup_variables.outputs.channel }}
61 version: ${{ steps.setup_variables.outputs.version }}
62 target_repo: ${{ steps.setup_variables.outputs.target_repo }}
63 target_repo_token: ${{ steps.setup_variables.outputs.target_repo_token }}
64 target_tag: ${{ steps.setup_variables.outputs.target_tag }}
65 pypi_project: ${{ steps.setup_variables.outputs.pypi_project }}
66 pypi_suffix: ${{ steps.setup_variables.outputs.pypi_suffix }}
67 pypi_token: ${{ steps.setup_variables.outputs.pypi_token }}
c4efa0ae 68 head_sha: ${{ steps.get_target.outputs.head_sha }}
29cb20bd
SS
69
70 steps:
5438593a 71 - uses: actions/checkout@v4
29cb20bd
SS
72 with:
73 fetch-depth: 0
74
75 - uses: actions/setup-python@v4
76 with:
77 python-version: "3.10"
78
1d03633c 79 - name: Process inputs
80 id: process_inputs
c4efa0ae 81 run: |
1d03633c 82 cat << EOF
83 ::group::Inputs
84 prerelease=${{ inputs.prerelease }}
85 source=${{ inputs.source }}
86 target=${{ inputs.target }}
87 version=${{ inputs.version }}
88 ::endgroup::
89 EOF
90 IFS='@' read -r source_repo source_tag <<<"${{ inputs.source }}"
91 IFS='@' read -r target_repo target_tag <<<"${{ inputs.target }}"
92 cat << EOF >> "$GITHUB_OUTPUT"
93 source_repo=${source_repo}
94 source_tag=${source_tag}
95 target_repo=${target_repo}
96 target_tag=${target_tag}
97 EOF
c4efa0ae 98
1d03633c 99 - name: Setup variables
100 id: setup_variables
101 env:
102 source_repo: ${{ steps.process_inputs.outputs.source_repo }}
103 source_tag: ${{ steps.process_inputs.outputs.source_tag }}
104 target_repo: ${{ steps.process_inputs.outputs.target_repo }}
105 target_tag: ${{ steps.process_inputs.outputs.target_tag }}
29cb20bd 106 run: |
1d03633c 107 # unholy bash monstrosity (sincere apologies)
108 fallback_token () {
109 if ${{ !secrets.ARCHIVE_REPO_TOKEN }}; then
110 echo "::error::Repository access secret ${target_repo_token^^} not found"
111 exit 1
112 fi
113 target_repo_token=ARCHIVE_REPO_TOKEN
114 return 0
115 }
116
117 source_is_channel=0
118 [[ "${source_repo}" == 'stable' ]] && source_repo='yt-dlp/yt-dlp'
119 if [[ -z "${source_repo}" ]]; then
120 source_repo='${{ github.repository }}'
121 elif [[ '${{ vars[format('{0}_archive_repo', env.source_repo)] }}' ]]; then
122 source_is_channel=1
123 source_channel='${{ vars[format('{0}_archive_repo', env.source_repo)] }}'
124 elif [[ -z "${source_tag}" && "${source_repo}" != */* ]]; then
125 source_tag="${source_repo}"
126 source_repo='${{ github.repository }}'
127 fi
128 resolved_source="${source_repo}"
129 if [[ "${source_tag}" ]]; then
130 resolved_source="${resolved_source}@${source_tag}"
131 elif [[ "${source_repo}" == 'yt-dlp/yt-dlp' ]]; then
132 resolved_source='stable'
133 fi
134
135 revision="${{ (inputs.prerelease || !vars.PUSH_VERSION_COMMIT) && '$(date -u +"%H%M%S")' || '' }}"
136 version="$(
20314dd4 137 python devscripts/update-version.py \
138 -c "${resolved_source}" -r "${{ github.repository }}" ${{ inputs.version || '$revision' }} | \
1d03633c 139 grep -Po "version=\K\d+\.\d+\.\d+(\.\d+)?")"
140
141 if [[ "${target_repo}" ]]; then
142 if [[ -z "${target_tag}" ]]; then
143 if [[ '${{ vars[format('{0}_archive_repo', env.target_repo)] }}' ]]; then
144 target_tag="${source_tag:-${version}}"
145 else
146 target_tag="${target_repo}"
147 target_repo='${{ github.repository }}'
148 fi
149 fi
150 if [[ "${target_repo}" != '${{ github.repository}}' ]]; then
151 target_repo='${{ vars[format('{0}_archive_repo', env.target_repo)] }}'
152 target_repo_token='${{ env.target_repo }}_archive_repo_token'
153 ${{ !!secrets[format('{0}_archive_repo_token', env.target_repo)] }} || fallback_token
154 pypi_project='${{ vars[format('{0}_pypi_project', env.target_repo)] }}'
155 pypi_suffix='${{ vars[format('{0}_pypi_suffix', env.target_repo)] }}'
156 ${{ !secrets[format('{0}_pypi_token', env.target_repo)] }} || pypi_token='${{ env.target_repo }}_pypi_token'
157 fi
158 else
159 target_tag="${source_tag:-${version}}"
160 if ((source_is_channel)); then
161 target_repo="${source_channel}"
162 target_repo_token='${{ env.source_repo }}_archive_repo_token'
163 ${{ !!secrets[format('{0}_archive_repo_token', env.source_repo)] }} || fallback_token
164 pypi_project='${{ vars[format('{0}_pypi_project', env.source_repo)] }}'
165 pypi_suffix='${{ vars[format('{0}_pypi_suffix', env.source_repo)] }}'
166 ${{ !secrets[format('{0}_pypi_token', env.source_repo)] }} || pypi_token='${{ env.source_repo }}_pypi_token'
167 else
168 target_repo='${{ github.repository }}'
169 fi
170 fi
171
172 if [[ "${target_repo}" == '${{ github.repository }}' ]] && ${{ !inputs.prerelease }}; then
173 pypi_project='${{ vars.PYPI_PROJECT }}'
174 fi
175 if [[ -z "${pypi_token}" && "${pypi_project}" ]]; then
176 if ${{ !secrets.PYPI_TOKEN }}; then
177 pypi_token=OIDC
178 else
179 pypi_token=PYPI_TOKEN
180 fi
181 fi
182
183 echo "::group::Output variables"
184 cat << EOF | tee -a "$GITHUB_OUTPUT"
185 channel=${resolved_source}
186 version=${version}
187 target_repo=${target_repo}
188 target_repo_token=${target_repo_token}
189 target_tag=${target_tag}
190 pypi_project=${pypi_project}
191 pypi_suffix=${pypi_suffix}
192 pypi_token=${pypi_token}
193 EOF
194 echo "::endgroup::"
29cb20bd
SS
195
196 - name: Update documentation
1d03633c 197 env:
198 version: ${{ steps.setup_variables.outputs.version }}
199 target_repo: ${{ steps.setup_variables.outputs.target_repo }}
200 if: |
201 !inputs.prerelease && env.target_repo == github.repository
29cb20bd
SS
202 run: |
203 make doc
204 sed '/### /Q' Changelog.md >> ./CHANGELOG
1d03633c 205 echo '### ${{ env.version }}' >> ./CHANGELOG
29cb20bd
SS
206 python ./devscripts/make_changelog.py -vv -c >> ./CHANGELOG
207 echo >> ./CHANGELOG
208 grep -Poz '(?s)### \d+\.\d+\.\d+.+' 'Changelog.md' | head -n -1 >> ./CHANGELOG
209 cat ./CHANGELOG > Changelog.md
210
211 - name: Push to release
212 id: push_release
1d03633c 213 env:
214 version: ${{ steps.setup_variables.outputs.version }}
215 target_repo: ${{ steps.setup_variables.outputs.target_repo }}
216 if: |
217 !inputs.prerelease && env.target_repo == github.repository
29cb20bd 218 run: |
24f82787 219 git config --global user.name "github-actions[bot]"
220 git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
29cb20bd 221 git add -u
1d03633c 222 git commit -m "Release ${{ env.version }}" \
29cb20bd
SS
223 -m "Created by: ${{ github.event.sender.login }}" -m ":ci skip all :ci run dl"
224 git push origin --force ${{ github.event.ref }}:release
c4efa0ae 225
226 - name: Get target commitish
227 id: get_target
228 run: |
29cb20bd
SS
229 echo "head_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
230
231 - name: Update master
1d03633c 232 env:
233 target_repo: ${{ steps.setup_variables.outputs.target_repo }}
234 if: |
235 vars.PUSH_VERSION_COMMIT != '' && !inputs.prerelease && env.target_repo == github.repository
29cb20bd
SS
236 run: git push origin ${{ github.event.ref }}
237
c4efa0ae 238 build:
29cb20bd 239 needs: prepare
c4efa0ae 240 uses: ./.github/workflows/build.yml
241 with:
242 version: ${{ needs.prepare.outputs.version }}
243 channel: ${{ needs.prepare.outputs.channel }}
20314dd4 244 origin: ${{ needs.prepare.outputs.target_repo }}
c4efa0ae 245 permissions:
246 contents: read
247 packages: write # For package cache
248 secrets:
249 GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
250
1d03633c 251 publish_pypi:
c4efa0ae 252 needs: [prepare, build]
1d03633c 253 if: ${{ needs.prepare.outputs.pypi_project }}
29cb20bd 254 runs-on: ubuntu-latest
1d03633c 255 permissions:
256 id-token: write # mandatory for trusted publishing
29cb20bd
SS
257
258 steps:
5438593a 259 - uses: actions/checkout@v4
29cb20bd
SS
260 - uses: actions/setup-python@v4
261 with:
262 python-version: "3.10"
263
264 - name: Install Requirements
265 run: |
1d03633c 266 sudo apt -y install pandoc man
29cb20bd
SS
267 python -m pip install -U pip setuptools wheel twine
268 python -m pip install -U -r requirements.txt
269
270 - name: Prepare
1d03633c 271 env:
272 version: ${{ needs.prepare.outputs.version }}
273 suffix: ${{ needs.prepare.outputs.pypi_suffix }}
274 channel: ${{ needs.prepare.outputs.channel }}
275 target_repo: ${{ needs.prepare.outputs.target_repo }}
276 pypi_project: ${{ needs.prepare.outputs.pypi_project }}
29cb20bd 277 run: |
1d03633c 278 python devscripts/update-version.py -c "${{ env.channel }}" -r "${{ env.target_repo }}" -s "${{ env.suffix }}" "${{ env.version }}"
29cb20bd 279 python devscripts/make_lazy_extractors.py
1d03633c 280 sed -i -E "s/(name=')[^']+(', # package name)/\1${{ env.pypi_project }}\2/" setup.py
29cb20bd 281
1d03633c 282 - name: Build
29cb20bd
SS
283 run: |
284 rm -rf dist/*
55676fe4 285 make pypi-files
29cb20bd
SS
286 python devscripts/set-variant.py pip -M "You installed yt-dlp with pip or using the wheel from PyPi; Use that to update"
287 python setup.py sdist bdist_wheel
29cb20bd 288
1d03633c 289 - name: Publish to PyPI via token
29cb20bd 290 env:
1d03633c 291 TWINE_USERNAME: __token__
292 TWINE_PASSWORD: ${{ secrets[needs.prepare.outputs.pypi_token] }}
293 if: |
294 needs.prepare.outputs.pypi_token != 'OIDC' && env.TWINE_PASSWORD
29cb20bd 295 run: |
1d03633c 296 twine upload dist/*
297
298 - name: Publish to PyPI via trusted publishing
299 if: |
300 needs.prepare.outputs.pypi_token == 'OIDC'
301 uses: pypa/gh-action-pypi-publish@release/v1
302 with:
303 verbose: true
29cb20bd 304
29cb20bd
SS
305 publish:
306 needs: [prepare, build]
29cb20bd
SS
307 permissions:
308 contents: write
1d03633c 309 runs-on: ubuntu-latest
310
311 steps:
5438593a 312 - uses: actions/checkout@v4
1d03633c 313 with:
314 fetch-depth: 0
315 - uses: actions/download-artifact@v3
316 - uses: actions/setup-python@v4
317 with:
318 python-version: "3.10"
319
320 - name: Generate release notes
321 env:
322 head_sha: ${{ needs.prepare.outputs.head_sha }}
323 target_repo: ${{ needs.prepare.outputs.target_repo }}
324 target_tag: ${{ needs.prepare.outputs.target_tag }}
325 run: |
326 printf '%s' \
327 '[![Installation](https://img.shields.io/badge/-Which%20file%20should%20I%20download%3F-white.svg?style=for-the-badge)]' \
328 '(https://github.com/${{ github.repository }}#installation "Installation instructions") ' \
329 '[![Documentation](https://img.shields.io/badge/-Docs-brightgreen.svg?style=for-the-badge&logo=GitBook&labelColor=555555)]' \
330 '(https://github.com/${{ github.repository }}' \
331 '${{ env.target_repo == github.repository && format('/tree/{0}', env.target_tag) || '' }}#readme "Documentation") ' \
332 '[![Donate](https://img.shields.io/badge/_-Donate-red.svg?logo=githubsponsors&labelColor=555555&style=for-the-badge)]' \
333 '(https://github.com/yt-dlp/yt-dlp/blob/master/Collaborators.md#collaborators "Donate") ' \
334 '[![Discord](https://img.shields.io/discord/807245652072857610?color=blue&labelColor=555555&label=&logo=discord&style=for-the-badge)]' \
335 '(https://discord.gg/H5MNcFW63r "Discord") ' \
336 ${{ env.target_repo == 'yt-dlp/yt-dlp' && '\
337 "[![Nightly](https://img.shields.io/badge/Get%20nightly%20builds-purple.svg?style=for-the-badge)]" \
338 "(https://github.com/yt-dlp/yt-dlp-nightly-builds/releases/latest \"Nightly builds\") " \
339 "[![Master](https://img.shields.io/badge/Get%20master%20builds-lightblue.svg?style=for-the-badge)]" \
340 "(https://github.com/yt-dlp/yt-dlp-master-builds/releases/latest \"Master builds\")"' || '' }} > ./RELEASE_NOTES
341 printf '\n\n' >> ./RELEASE_NOTES
342 cat >> ./RELEASE_NOTES << EOF
343 #### A description of the various files are in the [README](https://github.com/${{ github.repository }}#release-files)
344 ---
345 $(python ./devscripts/make_changelog.py -vv --collapsible)
346 EOF
347 printf '%s\n\n' '**This is a pre-release build**' >> ./PRERELEASE_NOTES
348 cat ./RELEASE_NOTES >> ./PRERELEASE_NOTES
349 printf '%s\n\n' 'Generated from: https://github.com/${{ github.repository }}/commit/${{ env.head_sha }}' >> ./ARCHIVE_NOTES
350 cat ./RELEASE_NOTES >> ./ARCHIVE_NOTES
351
352 - name: Publish to archive repo
353 env:
354 GH_TOKEN: ${{ secrets[needs.prepare.outputs.target_repo_token] }}
355 GH_REPO: ${{ needs.prepare.outputs.target_repo }}
356 version: ${{ needs.prepare.outputs.version }}
357 channel: ${{ needs.prepare.outputs.channel }}
358 if: |
359 inputs.prerelease && env.GH_TOKEN != '' && env.GH_REPO != '' && env.GH_REPO != github.repository
360 run: |
361 title="${{ startswith(env.GH_REPO, 'yt-dlp/') && 'yt-dlp ' || '' }}${{ env.channel }}"
362 gh release create \
363 --notes-file ARCHIVE_NOTES \
364 --title "${title} ${{ env.version }}" \
365 ${{ env.version }} \
366 artifact/*
367
368 - name: Prune old release
369 env:
370 GH_TOKEN: ${{ github.token }}
371 version: ${{ needs.prepare.outputs.version }}
372 target_repo: ${{ needs.prepare.outputs.target_repo }}
373 target_tag: ${{ needs.prepare.outputs.target_tag }}
374 if: |
375 env.target_repo == github.repository && env.target_tag != env.version
376 run: |
377 gh release delete --yes --cleanup-tag "${{ env.target_tag }}" || true
378 git tag --delete "${{ env.target_tag }}" || true
379 sleep 5 # Enough time to cover deletion race condition
380
381 - name: Publish release
382 env:
383 GH_TOKEN: ${{ github.token }}
384 version: ${{ needs.prepare.outputs.version }}
385 target_repo: ${{ needs.prepare.outputs.target_repo }}
386 target_tag: ${{ needs.prepare.outputs.target_tag }}
387 head_sha: ${{ needs.prepare.outputs.head_sha }}
388 if: |
389 env.target_repo == github.repository
390 run: |
391 title="${{ github.repository == 'yt-dlp/yt-dlp' && 'yt-dlp ' || '' }}"
392 title+="${{ env.target_tag != env.version && format('{0} ', env.target_tag) || '' }}"
393 gh release create \
394 --notes-file ${{ inputs.prerelease && 'PRERELEASE_NOTES' || 'RELEASE_NOTES' }} \
395 --target ${{ env.head_sha }} \
396 --title "${title}${{ env.version }}" \
397 ${{ inputs.prerelease && '--prerelease' || '' }} \
398 ${{ env.target_tag }} \
399 artifact/*