name: Release 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: ) required: false default: '' type: string target: description: | TARGET to publish this release to: channel, tag, or channel@tag (default: if writable else [@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 jobs: prepare: permissions: contents: write runs-on: ubuntu-latest outputs: 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@v4 with: fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.10" - name: Process inputs id: process_inputs run: | 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: | python devscripts/update_changelog.py -vv make doc - 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[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" git add -u 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 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 }} 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@v4 with: fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install Requirements run: | 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 -c "${{ env.channel }}" -r "${{ env.target_repo }}" -s "${{ env.suffix }}" "${{ env.version }}" python devscripts/update_changelog.py -vv python devscripts/make_lazy_extractors.py sed -i -E '0,/(name = ")[^"]+(")/s//\1${{ env.pypi_project }}\2/' pyproject.toml - name: Build run: | rm -rf dist/* make pypi-files printf '%s\n\n' \ 'Official repository: ' \ '**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" make clean-cache python -m build --no-isolation . - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: verbose: true publish: needs: [prepare, build] permissions: contents: write 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%20to%20download%3F-white.svg?style=for-the-badge)]' \ '(https://github.com/${{ github.repository }}#installation "Installation instructions") ' \ '[![Discord](https://img.shields.io/discord/807245652072857610?color=blue&labelColor=555555&label=&logo=discord&style=for-the-badge)]' \ '(https://discord.gg/H5MNcFW63r "Discord") ' \ '[![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") ' \ '[![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") ' \ ${{ env.target_repo == 'yt-dlp/yt-dlp' && '\ "[![Nightly](https://img.shields.io/badge/Nightly%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/Master%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/*