release #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: release | |
| # Trigger a release whenever the version file is edited on the main branch, or | |
| # manually rerun the current main release after fixing a workflow failure. | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "plexmuxy/VERSION" | |
| permissions: | |
| contents: write | |
| jobs: | |
| release: | |
| if: github.ref == 'refs/heads/main' | |
| runs-on: windows-latest | |
| env: | |
| WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }} | |
| WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.12" | |
| - name: Read version from VERSION file | |
| id: version | |
| shell: pwsh | |
| run: | | |
| $version = (Get-Content plexmuxy/VERSION).Trim() | |
| echo "version=$version" >> $env:GITHUB_OUTPUT | |
| - run: python -m pip install -e ".[dev,build,gui]" cyclonedx-bom | |
| - name: Install Inno Setup | |
| run: choco install innosetup -y --no-progress | |
| - run: python -c "import os; os.makedirs('.pytest-tmp', exist_ok=True)" && python -m pytest -m "not integration" --basetemp .pytest-tmp/release -o cache_dir=.pytest-tmp/cache | |
| - run: python -m build | |
| - name: Generate PyInstaller version-info resource | |
| shell: python | |
| run: | | |
| import re, pathlib | |
| version = pathlib.Path("plexmuxy/VERSION").read_text(encoding="utf-8").strip() | |
| m = re.match(r"^(\d+)\.(\d+)\.(\d+)", version) | |
| if not m: | |
| raise SystemExit(f"Cannot parse version '{version}' as MAJOR.MINOR.PATCH") | |
| filevers = f"{int(m[1])}, {int(m[2])}, {int(m[3])}, 0" | |
| info = f"""# UTF-8 | |
| # | |
| VSVersionInfo( | |
| ffi=FixedFileInfo( | |
| filevers=({filevers}), | |
| prodvers=({filevers}), | |
| mask=0x3f, | |
| flags=0x0, | |
| OS=0x40004, | |
| fileType=0x1, | |
| subtype=0x0, | |
| date=(0, 0) | |
| ), | |
| kids=[ | |
| StringFileInfo([ | |
| StringTable( | |
| u'040904B0', | |
| [StringStruct(u'CompanyName', u'PlexMuxy contributors'), | |
| StringStruct(u'FileDescription', u'PlexMuxy'), | |
| StringStruct(u'FileVersion', u'{version}'), | |
| StringStruct(u'InternalName', u'plexmuxy'), | |
| StringStruct(u'LegalCopyright', u'MIT License'), | |
| StringStruct(u'OriginalFilename', u'plexmuxy-gui.exe'), | |
| StringStruct(u'ProductName', u'PlexMuxy'), | |
| StringStruct(u'ProductVersion', u'{version}')]) | |
| ]), | |
| VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) | |
| ] | |
| ) | |
| """ | |
| pathlib.Path("packaging").mkdir(exist_ok=True) | |
| pathlib.Path("packaging/version_info.txt").write_text(info, encoding="utf-8") | |
| - run: python -m PyInstaller --clean --noconfirm plexmuxy-cli.spec | |
| - run: python -m PyInstaller --clean --noconfirm plexmuxy-gui.spec | |
| - name: Sign portable executables when a certificate is configured | |
| if: env.WINDOWS_CERTIFICATE_BASE64 != '' | |
| shell: pwsh | |
| run: | | |
| $certificate = Join-Path $env:RUNNER_TEMP "plexmuxy-signing.pfx" | |
| [IO.File]::WriteAllBytes($certificate, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_BASE64)) | |
| try { | |
| Get-ChildItem dist -Recurse -Filter *.exe | ForEach-Object { | |
| signtool sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /f $certificate /p $env:WINDOWS_CERTIFICATE_PASSWORD $_.FullName | |
| } | |
| } finally { | |
| Remove-Item -Force -ErrorAction SilentlyContinue $certificate | |
| } | |
| - name: Build Windows installer | |
| shell: pwsh | |
| run: iscc "/DMyAppVersion=${{ steps.version.outputs.version }}" packaging/plexmuxy.iss | |
| - name: Sign installer when a certificate is configured | |
| if: env.WINDOWS_CERTIFICATE_BASE64 != '' | |
| shell: pwsh | |
| run: | | |
| $certificate = Join-Path $env:RUNNER_TEMP "plexmuxy-signing.pfx" | |
| [IO.File]::WriteAllBytes($certificate, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_BASE64)) | |
| try { | |
| Get-ChildItem dist -File -Filter *-setup.exe | ForEach-Object { | |
| signtool sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /f $certificate /p $env:WINDOWS_CERTIFICATE_PASSWORD $_.FullName | |
| } | |
| } finally { | |
| Remove-Item -Force -ErrorAction SilentlyContinue $certificate | |
| } | |
| - name: Rename release artifacts | |
| shell: pwsh | |
| run: | | |
| $version = "${{ steps.version.outputs.version }}" | |
| Move-Item -Force "dist/plexmuxy-gui/plexmuxy-gui.exe" "dist/PlexMuxy-windows-amd64-$version.exe" | |
| Move-Item -Force "dist/plexmuxy-$version-windows-x64-setup.exe" "dist/PlexMuxy-windows-amd64-$version-setup.exe" | |
| - name: Generate release SBOM | |
| run: python -m cyclonedx_py environment --output-format JSON --output-file dist/plexmuxy-sbom.cdx.json | |
| - name: Generate checksums and manifest | |
| shell: pwsh | |
| run: | | |
| $artifacts = Get-ChildItem dist -File | |
| $manifest = [ordered]@{ | |
| version = "${{ steps.version.outputs.version }}" | |
| commit = $env:GITHUB_SHA | |
| python = (python --version) | |
| artifacts = @($artifacts | ForEach-Object { [ordered]@{ name = $_.Name; size = $_.Length; sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $_.FullName).Hash.ToLower() } }) | |
| } | |
| $manifest | ConvertTo-Json -Depth 5 | Set-Content -Encoding utf8 dist/release-manifest.json | |
| Get-ChildItem dist -File | Where-Object Name -ne 'SHA256SUMS.txt' | ForEach-Object { | |
| $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $_.FullName).Hash.ToLower() | |
| "$hash $($_.Name)" | Add-Content -Encoding ascii dist/SHA256SUMS.txt | |
| } | |
| - name: Create or refresh GitHub release | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| $version = "${{ steps.version.outputs.version }}" | |
| $tag = "v$version" | |
| $assets = @(Get-ChildItem dist -File | ForEach-Object FullName) | |
| $tagCommit = gh api "repos/$env:GITHUB_REPOSITORY/commits/$tag" --jq .sha 2>$null | |
| $tagExists = $LASTEXITCODE -eq 0 | |
| if ($tagExists -and $tagCommit.Trim() -ne $env:GITHUB_SHA) { | |
| throw "Tag $tag already points to $($tagCommit.Trim()), not $env:GITHUB_SHA. Bump plexmuxy/VERSION instead of moving a published tag." | |
| } | |
| gh release view $tag *> $null | |
| $releaseExists = $LASTEXITCODE -eq 0 | |
| if ($releaseExists) { | |
| gh release upload $tag $assets --clobber | |
| gh release edit $tag --title "PlexMuxy $tag" | |
| } elseif ($tagExists) { | |
| gh release create $tag $assets --verify-tag --generate-notes --title "PlexMuxy $tag" | |
| } else { | |
| gh release create $tag $assets --target $env:GITHUB_SHA --generate-notes --title "PlexMuxy $tag" | |
| } | |
| publish-pypi: | |
| name: Publish source and wheel with PyPI Trusted Publishing | |
| needs: release | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/plexmuxy | |
| permissions: | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.12" | |
| - run: python -m pip install build | |
| - run: python -m build | |
| - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 | |
| with: | |
| skip-existing: true |