diff --git a/.github/workflows/package-macos.yml b/.github/workflows/package-macos.yml new file mode 100644 index 000000000..6daf4de86 --- /dev/null +++ b/.github/workflows/package-macos.yml @@ -0,0 +1,71 @@ +name: Package for macOS + +on: + push: + paths: + - '.github/workflows/package-macos.yml' + - 'picard/**' + - 'po/**.po' + - 'resources/win10/**' + - '!scripts/package/*.ps1' + - 'scripts/pyinstaller/*' + - 'picard.icns' + - 'picard.spec' + - 'requirements*.txt' + - 'setup.py' + - 'tagger.py.in' + create: + tags: + - 'release-*' + +jobs: + package: + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Patch build version + if: startsWith(github.ref, 'refs/tags/') != true + run: | + python setup.py patch_version --platform=$(git rev-list --count HEAD).$(git rev-parse --short HEAD) + - name: Setup macOS build environment + run: | + ./scripts/package/macos-setup.sh + mkdir artifacts + env: + DISCID_VERSION: 0.6.2 + FPCALC_VERSION: 1.4.3 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + source bin/activate + pip install -r requirements-macos.txt + - name: Build macOS app + run: | + source bin/activate + ./scripts/package/macos-package-app.sh + mv dist/*.dmg artifacts/ + env: + APPLE_ID_USER: ${{ secrets. APPLE_ID_USER }} + APPLE_ID_PASSWORD: ${{ secrets. APPLE_ID_PASSWORD }} + encrypted_be5fb2212036_key: ${{ secrets.CODESIGN_MACOS_ENCRYPTED_KEY }} + encrypted_be5fb2212036_iv: ${{ secrets.CODESIGN_MACOS_ENCRYPTED_IV }} + appledev_p12_password: ${{ secrets.CODESIGN_MACOS_P12_PASSWORD }} + - name: Archive production artifacts + uses: actions/upload-artifact@v1 + if: always() + with: + name: macos-builds + path: artifacts/ + - name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'create' + with: + files: artifacts/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package-windows.yml b/.github/workflows/package-windows.yml new file mode 100644 index 000000000..86ed95b77 --- /dev/null +++ b/.github/workflows/package-windows.yml @@ -0,0 +1,99 @@ +name: Package for Windows + +on: + push: + paths: + - '.github/workflows/package-windows.yml' + - 'installer/**' + - 'picard/**' + - 'po/**.po' + - 'resources/win10/**' + - 'scripts/package/*.ps1' + - 'scripts/pyinstaller/*' + - 'appxmanifest.xml.in' + - 'picard.ico' + - 'picard.spec' + - 'requirements*.txt' + - 'setup.py' + - 'tagger.py.in' + - 'win.version-info.txt.in' + create: + tags: + - 'release-*' + +jobs: + package: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Patch build version + if: startsWith(github.ref, 'refs/tags/') != true + run: | + python setup.py patch_version --platform=$(git rev-list --count HEAD).$(git rev-parse --short HEAD) + - name: Setup Windows build environment + run: | + & .\scripts\package\win-setup.ps1 -DiscidVersion $Env:DISCID_VERSION -FpcalVersion $Env:FPCALC_VERSION + New-Item -Name .\artifacts -ItemType Directory + env: + DISCID_VERSION: 0.6.2 + FPCALC_VERSION: 1.4.3 + - name: Prepare code signing certificate + run: | + pip install awscli + aws s3 cp "$Env:CODESIGN_PFX_URL" .\codesign.pfx + env: + AWS_DEFAULT_REGION: eu-central-1 + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + CODESIGN_PFX_URL: ${{ secrets.CODESIGN_PFX_URL }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-build.txt + pip install -r requirements-win.txt + - name: Build Windows 10 app package + run: | + $Env:PATH += ";C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64" + & .\scripts\package\win-package-appx.ps1 -BuildNumber $(git rev-list --count HEAD) -CertificateFile .\codesign.pfx -CertificatePassword $Env:CODESIGN_PFX_PASSWORD + Move-Item .\dist\*.msix .\artifacts + env: + CODESIGN_PFX_PASSWORD: ${{ secrets.CODESIGN_PFX_PASSWORD }} + - name: Build Windows installer + if: always() + run: | + # choco install nsis + $CertPassword = ConvertTo-SecureString -String $Env:CODESIGN_PFX_PASSWORD -Force -AsPlainText + $Certificate = Get-PfxCertificate -FilePath .\codesign.pfx -Password $CertPassword + & .\scripts\package\win-package-installer.ps1 -BuildNumber $(git rev-list --count HEAD) -Certificate $Certificate + Move-Item .\installer\*.exe .\artifacts + dist\picard\fpcalc -version + env: + CODESIGN_PFX_PASSWORD: ${{ secrets.CODESIGN_PFX_PASSWORD }} + - name: Build Windows portable app + if: always() + run: | + $CertPassword = ConvertTo-SecureString -String $Env:CODESIGN_PFX_PASSWORD -Force -AsPlainText + $Certificate = Get-PfxCertificate -FilePath .\codesign.pfx -Password $CertPassword + & .\scripts\package\win-package-portable.ps1 -BuildNumber $(git rev-list --count HEAD) -Certificate $Certificate + Move-Item .\dist\*.exe .\artifacts + env: + CODESIGN_PFX_PASSWORD: ${{ secrets.CODESIGN_PFX_PASSWORD }} + - name: Archive production artifacts + uses: actions/upload-artifact@v1 + if: always() + with: + name: windows-builds + path: artifacts/ + - name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'create' + with: + files: artifacts/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 000000000..fb0c906f8 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,68 @@ +name: Run tests + +on: push + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + python-version: [3.5, 3.6, 3.7, 3.8] + env: + CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Check coding style + run: | + pip install flake8 isort + flake8 picard --count --show-source --statistics + isort --check-only --diff --recursive picard test + - name: Test with pytest + if: always() + run: | + pip install pytest pytest-randomly pytest-cov + pytest --verbose --cov=picard --cov-report xml:coverage.xml test + - name: Submit code coverage to Codacy + if: env.CODACY_PROJECT_TOKEN + run: | + pip install codacy-coverage + python-codacy-coverage -r coverage.xml + + pip-install: # Test whether a clean pip install from source works + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + python-version: [3.8] + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install gettext (Linux) + if: runner.os == 'Linux' + run: sudo apt-get install gettext + - name: Install gettext (macOS) + if: runner.os == 'macOS' + run: | + brew install gettext + brew link gettext --force + - name: Run pip install . + run: | + python -m pip install --upgrade pip + pip install . + - name: Test running installed package + if: runner.os != 'Windows' + run: picard --long-version diff --git a/scripts/package/macos-package-app.sh b/scripts/package/macos-package-app.sh index 7fb41b079..febeac4d0 100755 --- a/scripts/package/macos-package-app.sh +++ b/scripts/package/macos-package-app.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash set -e -if [ -z "$TRAVIS_TAG" ] -then +if [ -z "$TRAVIS_TAG" ] && [ -n "$TRAVIS_OSX_IMAGE" ]; then python3 setup.py patch_version --platform=osx.$TRAVIS_OSX_IMAGE fi VERSION=$(python3 -c 'import picard; print(picard.__version__)') @@ -78,7 +77,11 @@ LIBDISCID_REGEX="libdiscid [0-9]+\.[0-9]+\.[0-9]+" "MusicBrainz Picard.app/Contents/MacOS/fpcalc" -version # Package app bundle into DMG image -DMG="MusicBrainz Picard $VERSION macOS $MACOS_VERSION_MAJOR.$MACOS_VERSION_MINOR.dmg" +if [ -n "$TRAVIS_OSX_IMAGE" ]; then + DMG="MusicBrainz-Picard-${VERSION}_macOS-$MACOS_VERSION_MAJOR.$MACOS_VERSION_MINOR.dmg" +else + DMG="MusicBrainz-Picard-$VERSION.dmg" +fi hdiutil create -volname "MusicBrainz Picard $VERSION" \ -srcfolder 'MusicBrainz Picard.app' -ov -format UDBZ "$DMG" [ "$CODESIGN" = '1' ] && codesign --verify --verbose \ diff --git a/scripts/package/macos-setup.sh b/scripts/package/macos-setup.sh index 5c6f72a0a..82e201942 100755 --- a/scripts/package/macos-setup.sh +++ b/scripts/package/macos-setup.sh @@ -7,9 +7,11 @@ brew install gettext brew link gettext --force # Install requested Python version -wget "https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-macosx10.9.pkg" -sudo installer -pkg python-${PYTHON_VERSION}-macosx10.9.pkg -target / -sudo python3 -m ensurepip +if [ -n "$PYTHON_VERSION" ]; then + wget "https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-macosx10.9.pkg" + sudo installer -pkg python-${PYTHON_VERSION}-macosx10.9.pkg -target / + sudo python3 -m ensurepip +fi # Install libdiscid wget "ftp://ftp.musicbrainz.org/pub/musicbrainz/libdiscid/libdiscid-$DISCID_VERSION.tar.gz" diff --git a/scripts/package/win-package-appx.ps1 b/scripts/package/win-package-appx.ps1 index 392b88442..10efebcc3 100644 --- a/scripts/package/win-package-appx.ps1 +++ b/scripts/package/win-package-appx.ps1 @@ -3,14 +3,28 @@ Param( [System.Security.Cryptography.X509Certificates.X509Certificate] $Certificate, + [ValidateScript({Test-Path $_ -PathType Leaf})] + [String] + $CertificateFile, + [String] + $CertificatePassword, [Int] $BuildNumber ) +# Errors are handled explicitly. Otherwise any output to stderr when +# calling classic Windows exes causes a script error. +$ErrorActionPreference = 'Continue' + If (-Not $BuildNumber) { $BuildNumber = 0 } +If (-Not $Certificate -And $CertificateFile) { + $CertPassword = ConvertTo-SecureString -String $CertificatePassword -Force -AsPlainText + $Certificate = Get-PfxCertificate -FilePath $CertificateFile -Password $CertPassword +} + $ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent . $ScriptDirectory\win-common.ps1 -Certificate $Certificate @@ -43,12 +57,15 @@ Pop-Location # Generate msix package $PicardVersion = (python -c "import picard; print(picard.__version__)") -$PackageFile = "dist\MusicBrainz Picard $PicardVersion.msix" +$PackageFile = "dist\MusicBrainz-Picard-$PicardVersion.msix" MakeAppx pack /o /h SHA256 /d $PackageDir /p $PackageFile ThrowOnExeError "MakeAppx failed" # Sign package -If ($Certificate) { +If ($CertificateFile) { + SignTool sign /fd SHA256 /f "$CertificateFile" /p "$CertificatePassword" $PackageFile + ThrowOnExeError "SignTool failed" +} ElseIf ($Certificate) { SignTool sign /fd SHA256 /sha1 $Certificate.Thumbprint $PackageFile ThrowOnExeError "SignTool failed" } diff --git a/scripts/package/win-package-installer.ps1 b/scripts/package/win-package-installer.ps1 index 1efb752e3..5a075bb97 100644 --- a/scripts/package/win-package-installer.ps1 +++ b/scripts/package/win-package-installer.ps1 @@ -7,6 +7,10 @@ Param( $BuildNumber ) +# Errors are handled explicitly. Otherwise any output to stderr when +# calling classic Windows exes causes a script error. +$ErrorActionPreference = 'Continue' + If (-Not $BuildNumber) { $BuildNumber = 0 } diff --git a/scripts/package/win-package-portable.ps1 b/scripts/package/win-package-portable.ps1 index afe333338..8b5394eed 100644 --- a/scripts/package/win-package-portable.ps1 +++ b/scripts/package/win-package-portable.ps1 @@ -7,6 +7,10 @@ Param( $BuildNumber ) +# Errors are handled explicitly. Otherwise any output to stderr when +# calling classic Windows exes causes a script error. +$ErrorActionPreference = 'Continue' + If (-Not $BuildNumber) { $BuildNumber = 0 } diff --git a/scripts/package/win-setup.ps1 b/scripts/package/win-setup.ps1 new file mode 100644 index 000000000..ea2c49ff7 --- /dev/null +++ b/scripts/package/win-setup.ps1 @@ -0,0 +1,39 @@ +Param( + [Parameter(Mandatory=$true)] + [String] + $DiscidVersion, + [Parameter(Mandatory=$true)] + [String] + $FpcalVersion +) + +$ErrorActionPreference = "Stop" + +Function DownloadFile { + Param( + [Parameter(Mandatory=$true)] + [String] + $FileName, + [Parameter(Mandatory=$true)] + [String] + $Url + ) + $OutputPath = (Join-Path (Resolve-Path .) $FileName) + (New-Object System.Net.WebClient).DownloadFile($Url, "$OutputPath") +} + +New-Item -Name .\build -ItemType Directory -ErrorAction Ignore + +$ArchiveFile = ".\build\libdiscid.zip" +Write-Output "Downloading libdiscid to $ArchiveFile..." +DownloadFile -Url "https://github.com/metabrainz/libdiscid/releases/download/v$DiscidVersion/libdiscid-$DiscidVersion-win64.zip" ` + -FileName $ArchiveFile +Expand-Archive -Path $ArchiveFile -DestinationPath .\build\libdiscid -Force +Copy-Item .\build\libdiscid\discid.dll . + +$ArchiveFile = ".\build\fpcalc.zip" +Write-Output "Downloading chromaprint-fpcalc to $ArchiveFile..." +DownloadFile -Url "https://github.com/acoustid/chromaprint/releases/download/v$FpcalVersion/chromaprint-fpcalc-$FpcalVersion-windows-x86_64.zip" ` + -FileName $ArchiveFile +Expand-Archive -Path $ArchiveFile -DestinationPath .\build\fpcalc -Force +Copy-Item .\build\fpcalc\chromaprint-fpcalc-$FpcalVersion-windows-x86_64\fpcalc.exe .