]> jfr.im git - yt-dlp.git/blobdiff - .github/workflows/release.yml
[build] Bump `actions/upload-artifact` to v4 and adjust workflows
[yt-dlp.git] / .github / workflows / release.yml
index 329d49af86a15b038279d56898cbc738bf741a12..f5c6a793e19dc68a3018db8a9b39aedabcc51793 100644 (file)
@@ -1,5 +1,53 @@
 name: Release
-on: workflow_dispatch
+on:
+  workflow_call:
+    inputs:
+      prerelease:
+        required: false
+        default: true
+        type: boolean
+      source:
+        required: false
+        default: ''
+        type: string
+      target:
+        required: false
+        default: ''
+        type: string
+      version:
+        required: false
+        default: ''
+        type: string
+  workflow_dispatch:
+    inputs:
+      source:
+        description: |
+          SOURCE of this release's updates:
+          channel, repo, tag, or channel/repo@tag
+          (default: <current_repo>)
+        required: false
+        default: ''
+        type: string
+      target:
+        description: |
+          TARGET to publish this release to:
+          channel, tag, or channel@tag
+          (default: <source> if writable else <current_repo>[@source_tag])
+        required: false
+        default: ''
+        type: string
+      version:
+        description: |
+          VERSION: yyyy.mm.dd[.rev] or rev
+          (default: auto-generated)
+        required: false
+        default: ''
+        type: string
+      prerelease:
+        description: Pre-release
+        default: false
+        type: boolean
+
 permissions:
   contents: read
 
@@ -9,29 +57,141 @@ jobs:
       contents: write
     runs-on: ubuntu-latest
     outputs:
-      version: ${{ steps.update_version.outputs.version }}
-      head_sha: ${{ steps.push_release.outputs.head_sha }}
+      channel: ${{ steps.setup_variables.outputs.channel }}
+      version: ${{ steps.setup_variables.outputs.version }}
+      target_repo: ${{ steps.setup_variables.outputs.target_repo }}
+      target_repo_token: ${{ steps.setup_variables.outputs.target_repo_token }}
+      target_tag: ${{ steps.setup_variables.outputs.target_tag }}
+      pypi_project: ${{ steps.setup_variables.outputs.pypi_project }}
+      pypi_suffix: ${{ steps.setup_variables.outputs.pypi_suffix }}
+      head_sha: ${{ steps.get_target.outputs.head_sha }}
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 0
 
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: "3.10"
 
-      - name: Update version
-        id: update_version
+      - name: Process inputs
+        id: process_inputs
         run: |
-          python devscripts/update-version.py ${{ vars.PUSH_VERSION_COMMIT == '' && '"$(date -u +"%H%M%S")"' || '' }} | \
-            grep -Po "version=\d+\.\d+\.\d+(\.\d+)?" >> "$GITHUB_OUTPUT"
+          cat << EOF
+          ::group::Inputs
+          prerelease=${{ inputs.prerelease }}
+          source=${{ inputs.source }}
+          target=${{ inputs.target }}
+          version=${{ inputs.version }}
+          ::endgroup::
+          EOF
+          IFS='@' read -r source_repo source_tag <<<"${{ inputs.source }}"
+          IFS='@' read -r target_repo target_tag <<<"${{ inputs.target }}"
+          cat << EOF >> "$GITHUB_OUTPUT"
+          source_repo=${source_repo}
+          source_tag=${source_tag}
+          target_repo=${target_repo}
+          target_tag=${target_tag}
+          EOF
+
+      - name: Setup variables
+        id: setup_variables
+        env:
+          source_repo: ${{ steps.process_inputs.outputs.source_repo }}
+          source_tag: ${{ steps.process_inputs.outputs.source_tag }}
+          target_repo: ${{ steps.process_inputs.outputs.target_repo }}
+          target_tag: ${{ steps.process_inputs.outputs.target_tag }}
+        run: |
+          # unholy bash monstrosity (sincere apologies)
+          fallback_token () {
+            if ${{ !secrets.ARCHIVE_REPO_TOKEN }}; then
+              echo "::error::Repository access secret ${target_repo_token^^} not found"
+              exit 1
+            fi
+            target_repo_token=ARCHIVE_REPO_TOKEN
+            return 0
+          }
+
+          source_is_channel=0
+          [[ "${source_repo}" == 'stable' ]] && source_repo='yt-dlp/yt-dlp'
+          if [[ -z "${source_repo}" ]]; then
+            source_repo='${{ github.repository }}'
+          elif [[ '${{ vars[format('{0}_archive_repo', env.source_repo)] }}' ]]; then
+            source_is_channel=1
+            source_channel='${{ vars[format('{0}_archive_repo', env.source_repo)] }}'
+          elif [[ -z "${source_tag}" && "${source_repo}" != */* ]]; then
+            source_tag="${source_repo}"
+            source_repo='${{ github.repository }}'
+          fi
+          resolved_source="${source_repo}"
+          if [[ "${source_tag}" ]]; then
+            resolved_source="${resolved_source}@${source_tag}"
+          elif [[ "${source_repo}" == 'yt-dlp/yt-dlp' ]]; then
+            resolved_source='stable'
+          fi
+
+          revision="${{ (inputs.prerelease || !vars.PUSH_VERSION_COMMIT) && '$(date -u +"%H%M%S")' || '' }}"
+          version="$(
+            python devscripts/update-version.py \
+            -c "${resolved_source}" -r "${{ github.repository }}" ${{ inputs.version || '$revision' }} | \
+            grep -Po "version=\K\d+\.\d+\.\d+(\.\d+)?")"
+
+          if [[ "${target_repo}" ]]; then
+            if [[ -z "${target_tag}" ]]; then
+              if [[ '${{ vars[format('{0}_archive_repo', env.target_repo)] }}' ]]; then
+                target_tag="${source_tag:-${version}}"
+              else
+                target_tag="${target_repo}"
+                target_repo='${{ github.repository }}'
+              fi
+            fi
+            if [[ "${target_repo}" != '${{ github.repository}}' ]]; then
+              target_repo='${{ vars[format('{0}_archive_repo', env.target_repo)] }}'
+              target_repo_token='${{ env.target_repo }}_archive_repo_token'
+              ${{ !!secrets[format('{0}_archive_repo_token', env.target_repo)] }} || fallback_token
+              pypi_project='${{ vars[format('{0}_pypi_project', env.target_repo)] }}'
+              pypi_suffix='${{ vars[format('{0}_pypi_suffix', env.target_repo)] }}'
+            fi
+          else
+            target_tag="${source_tag:-${version}}"
+            if ((source_is_channel)); then
+              target_repo="${source_channel}"
+              target_repo_token='${{ env.source_repo }}_archive_repo_token'
+              ${{ !!secrets[format('{0}_archive_repo_token', env.source_repo)] }} || fallback_token
+              pypi_project='${{ vars[format('{0}_pypi_project', env.source_repo)] }}'
+              pypi_suffix='${{ vars[format('{0}_pypi_suffix', env.source_repo)] }}'
+            else
+              target_repo='${{ github.repository }}'
+            fi
+          fi
+
+          if [[ "${target_repo}" == '${{ github.repository }}' ]] && ${{ !inputs.prerelease }}; then
+            pypi_project='${{ vars.PYPI_PROJECT }}'
+          fi
+
+          echo "::group::Output variables"
+          cat << EOF | tee -a "$GITHUB_OUTPUT"
+          channel=${resolved_source}
+          version=${version}
+          target_repo=${target_repo}
+          target_repo_token=${target_repo_token}
+          target_tag=${target_tag}
+          pypi_project=${pypi_project}
+          pypi_suffix=${pypi_suffix}
+          EOF
+          echo "::endgroup::"
 
       - name: Update documentation
+        env:
+          version: ${{ steps.setup_variables.outputs.version }}
+          target_repo: ${{ steps.setup_variables.outputs.target_repo }}
+        if: |
+          !inputs.prerelease && env.target_repo == github.repository
         run: |
           make doc
           sed '/### /Q' Changelog.md >> ./CHANGELOG
-          echo '### ${{ steps.update_version.outputs.version }}' >> ./CHANGELOG
+          echo '### ${{ env.version }}' >> ./CHANGELOG
           python ./devscripts/make_changelog.py -vv -c >> ./CHANGELOG
           echo >> ./CHANGELOG
           grep -Poz '(?s)### \d+\.\d+\.\d+.+' 'Changelog.md' | head -n -1 >> ./CHANGELOG
@@ -39,89 +199,189 @@ jobs:
 
       - name: Push to release
         id: push_release
+        env:
+          version: ${{ steps.setup_variables.outputs.version }}
+          target_repo: ${{ steps.setup_variables.outputs.target_repo }}
+        if: |
+          !inputs.prerelease && env.target_repo == github.repository
         run: |
-          git config --global user.name github-actions
-          git config --global user.email github-actions@example.com
+          git config --global user.name "github-actions[bot]"
+          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
           git add -u
-          git commit -m "Release ${{ steps.update_version.outputs.version }}" \
+          git commit -m "Release ${{ env.version }}" \
             -m "Created by: ${{ github.event.sender.login }}" -m ":ci skip all :ci run dl"
           git push origin --force ${{ github.event.ref }}:release
+
+      - name: Get target commitish
+        id: get_target
+        run: |
           echo "head_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
 
       - name: Update master
-        if: vars.PUSH_VERSION_COMMIT != ''
+        env:
+          target_repo: ${{ steps.setup_variables.outputs.target_repo }}
+        if: |
+          vars.PUSH_VERSION_COMMIT != '' && !inputs.prerelease && env.target_repo == github.repository
         run: git push origin ${{ github.event.ref }}
 
-  publish_pypi_homebrew:
+  build:
     needs: prepare
+    uses: ./.github/workflows/build.yml
+    with:
+      version: ${{ needs.prepare.outputs.version }}
+      channel: ${{ needs.prepare.outputs.channel }}
+      origin: ${{ needs.prepare.outputs.target_repo }}
+    permissions:
+      contents: read
+      packages: write # For package cache
+    secrets:
+      GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
+
+  publish_pypi:
+    needs: [prepare, build]
+    if: ${{ needs.prepare.outputs.pypi_project }}
     runs-on: ubuntu-latest
+    permissions:
+      id-token: write  # mandatory for trusted publishing
 
     steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-python@v4
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+      - uses: actions/setup-python@v5
         with:
           python-version: "3.10"
 
       - name: Install Requirements
         run: |
-          python -m pip install -U pip setuptools wheel twine
-          python -m pip install -U -r requirements.txt
+          sudo apt -y install pandoc man
+          python devscripts/install_deps.py -o --include build
 
       - name: Prepare
+        env:
+          version: ${{ needs.prepare.outputs.version }}
+          suffix: ${{ needs.prepare.outputs.pypi_suffix }}
+          channel: ${{ needs.prepare.outputs.channel }}
+          target_repo: ${{ needs.prepare.outputs.target_repo }}
+          pypi_project: ${{ needs.prepare.outputs.pypi_project }}
         run: |
-          python devscripts/update-version.py ${{ needs.prepare.outputs.version }}
+          python devscripts/update-version.py -c "${{ env.channel }}" -r "${{ env.target_repo }}" -s "${{ env.suffix }}" "${{ env.version }}"
           python devscripts/make_lazy_extractors.py
+          sed -i -E '0,/(name = ")[^"]+(")/s//\1${{ env.pypi_project }}\2/' pyproject.toml
 
-      - name: Build and publish on PyPI
-        env:
-          TWINE_USERNAME: __token__
-          TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
-        if: env.TWINE_PASSWORD != ''
+      - name: Build
         run: |
           rm -rf dist/*
+          make pypi-files
+          printf '%s\n\n' \
+            'Official repository: <https://github.com/yt-dlp/yt-dlp>' \
+            '**PS**: Some links in this document will not work since this is a copy of the README.md from Github' > ./README.md.new
+          cat ./README.md >> ./README.md.new && mv -f ./README.md.new ./README.md
           python devscripts/set-variant.py pip -M "You installed yt-dlp with pip or using the wheel from PyPi; Use that to update"
-          python setup.py sdist bdist_wheel
-          twine upload dist/*
+          make clean-cache
+          python -m build --no-isolation .
 
-      - name: Checkout Homebrew repository
-        env:
-          BREW_TOKEN: ${{ secrets.BREW_TOKEN }}
-          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
-        if: env.BREW_TOKEN != '' && env.PYPI_TOKEN != ''
-        uses: actions/checkout@v3
+      - name: Publish to PyPI
+        uses: pypa/gh-action-pypi-publish@release/v1
         with:
-          repository: yt-dlp/homebrew-taps
-          path: taps
-          ssh-key: ${{ secrets.BREW_TOKEN }}
-
-      - name: Update Homebrew Formulae
-        env:
-          BREW_TOKEN: ${{ secrets.BREW_TOKEN }}
-          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
-        if: env.BREW_TOKEN != '' && env.PYPI_TOKEN != ''
-        run: |
-          python devscripts/update-formulae.py taps/Formula/yt-dlp.rb "${{ needs.prepare.outputs.version }}"
-          git -C taps/ config user.name github-actions
-          git -C taps/ config user.email github-actions@example.com
-          git -C taps/ commit -am 'yt-dlp: ${{ needs.prepare.outputs.version }}'
-          git -C taps/ push
-
-  build:
-    needs: prepare
-    uses: ./.github/workflows/build.yml
-    with:
-      version: ${{ needs.prepare.outputs.version }}
-    permissions:
-      contents: read
-      packages: write # For package cache
-    secrets:
-      GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
+          verbose: true
 
   publish:
     needs: [prepare, build]
-    uses: ./.github/workflows/publish.yml
     permissions:
       contents: write
-    with:
-      version: ${{ needs.prepare.outputs.version }}
-      target_commitish: ${{ needs.prepare.outputs.head_sha }}
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+      - uses: actions/download-artifact@v4
+        with:
+          path: artifact
+          pattern: build-*
+          merge-multiple: true
+      - uses: actions/setup-python@v5
+        with:
+          python-version: "3.10"
+
+      - name: Generate release notes
+        env:
+          head_sha: ${{ needs.prepare.outputs.head_sha }}
+          target_repo: ${{ needs.prepare.outputs.target_repo }}
+          target_tag: ${{ needs.prepare.outputs.target_tag }}
+        run: |
+          printf '%s' \
+            '[![Installation](https://img.shields.io/badge/-Which%20file%20should%20I%20download%3F-white.svg?style=for-the-badge)]' \
+              '(https://github.com/${{ github.repository }}#installation "Installation instructions") ' \
+            '[![Documentation](https://img.shields.io/badge/-Docs-brightgreen.svg?style=for-the-badge&logo=GitBook&labelColor=555555)]' \
+              '(https://github.com/${{ github.repository }}' \
+              '${{ env.target_repo == github.repository && format('/tree/{0}', env.target_tag) || '' }}#readme "Documentation") ' \
+            '[![Donate](https://img.shields.io/badge/_-Donate-red.svg?logo=githubsponsors&labelColor=555555&style=for-the-badge)]' \
+              '(https://github.com/yt-dlp/yt-dlp/blob/master/Collaborators.md#collaborators "Donate") ' \
+            '[![Discord](https://img.shields.io/discord/807245652072857610?color=blue&labelColor=555555&label=&logo=discord&style=for-the-badge)]' \
+              '(https://discord.gg/H5MNcFW63r "Discord") ' \
+            ${{ env.target_repo == 'yt-dlp/yt-dlp' && '\
+              "[![Nightly](https://img.shields.io/badge/Get%20nightly%20builds-purple.svg?style=for-the-badge)]" \
+              "(https://github.com/yt-dlp/yt-dlp-nightly-builds/releases/latest \"Nightly builds\") " \
+              "[![Master](https://img.shields.io/badge/Get%20master%20builds-lightblue.svg?style=for-the-badge)]" \
+              "(https://github.com/yt-dlp/yt-dlp-master-builds/releases/latest \"Master builds\")"' || '' }} > ./RELEASE_NOTES
+          printf '\n\n' >> ./RELEASE_NOTES
+          cat >> ./RELEASE_NOTES << EOF
+          #### A description of the various files are in the [README](https://github.com/${{ github.repository }}#release-files)
+          ---
+          $(python ./devscripts/make_changelog.py -vv --collapsible)
+          EOF
+          printf '%s\n\n' '**This is a pre-release build**' >> ./PRERELEASE_NOTES
+          cat ./RELEASE_NOTES >> ./PRERELEASE_NOTES
+          printf '%s\n\n' 'Generated from: https://github.com/${{ github.repository }}/commit/${{ env.head_sha }}' >> ./ARCHIVE_NOTES
+          cat ./RELEASE_NOTES >> ./ARCHIVE_NOTES
+
+      - name: Publish to archive repo
+        env:
+          GH_TOKEN: ${{ secrets[needs.prepare.outputs.target_repo_token] }}
+          GH_REPO: ${{ needs.prepare.outputs.target_repo }}
+          version: ${{ needs.prepare.outputs.version }}
+          channel: ${{ needs.prepare.outputs.channel }}
+        if: |
+          inputs.prerelease && env.GH_TOKEN != '' && env.GH_REPO != '' && env.GH_REPO != github.repository
+        run: |
+          title="${{ startswith(env.GH_REPO, 'yt-dlp/') && 'yt-dlp ' || '' }}${{ env.channel }}"
+          gh release create \
+            --notes-file ARCHIVE_NOTES \
+            --title "${title} ${{ env.version }}" \
+            ${{ env.version }} \
+            artifact/*
+
+      - name: Prune old release
+        env:
+          GH_TOKEN: ${{ github.token }}
+          version: ${{ needs.prepare.outputs.version }}
+          target_repo: ${{ needs.prepare.outputs.target_repo }}
+          target_tag: ${{ needs.prepare.outputs.target_tag }}
+        if: |
+          env.target_repo == github.repository && env.target_tag != env.version
+        run: |
+          gh release delete --yes --cleanup-tag "${{ env.target_tag }}" || true
+          git tag --delete "${{ env.target_tag }}" || true
+          sleep 5  # Enough time to cover deletion race condition
+
+      - name: Publish release
+        env:
+          GH_TOKEN: ${{ github.token }}
+          version: ${{ needs.prepare.outputs.version }}
+          target_repo: ${{ needs.prepare.outputs.target_repo }}
+          target_tag: ${{ needs.prepare.outputs.target_tag }}
+          head_sha: ${{ needs.prepare.outputs.head_sha }}
+        if: |
+          env.target_repo == github.repository
+        run: |
+          title="${{ github.repository == 'yt-dlp/yt-dlp' && 'yt-dlp ' || '' }}"
+          title+="${{ env.target_tag != env.version && format('{0} ', env.target_tag) || '' }}"
+          gh release create \
+            --notes-file ${{ inputs.prerelease && 'PRERELEASE_NOTES' || 'RELEASE_NOTES' }} \
+            --target ${{ env.head_sha }} \
+            --title "${title}${{ env.version }}" \
+            ${{ inputs.prerelease && '--prerelease' || '' }} \
+            ${{ env.target_tag }} \
+            artifact/*