diff --git a/.github/workflows/release-linux.yaml b/.github/workflows/release-linux.yaml index fc49a49..29bb93e 100644 --- a/.github/workflows/release-linux.yaml +++ b/.github/workflows/release-linux.yaml @@ -4,7 +4,9 @@ # while POCO will be linked statically. name: Build Release Package for Linux -on: workflow_dispatch +on: + workflow_dispatch: + workflow_call: jobs: build-deb: diff --git a/.github/workflows/release-macos.yaml b/.github/workflows/release-macos.yaml index 3b32b48..46beebb 100644 --- a/.github/workflows/release-macos.yaml +++ b/.github/workflows/release-macos.yaml @@ -2,11 +2,26 @@ # Builds a universal binary on macOS. name: Build Release Package for macOS -on: workflow_dispatch +on: + workflow_dispatch: + workflow_call: + secrets: + MACOS_CERTIFICATE_APPLICATION: + required: true + MACOS_CERTIFICATE_INSTALLER: + required: true + MACOS_CERTIFICATE_PASSWORD: + required: true + MACOS_NOTARY_API_KEY: + required: true + MACOS_NOTARY_KEY_ID: + required: true + MACOS_NOTARY_ISSUER_ID: + required: true jobs: - build-deb: - name: ProductBuild Installer, macOS x86_64+arm64 + build-pkg: + name: ProductBuild and DMG Installers, macOS x86_64+arm64 runs-on: macos-latest steps: @@ -102,6 +117,27 @@ jobs: repository: projectM-visualizer/presets-milkdrop-texture-pack path: presets-milkdrop-texture-pack + - name: Import Code Signing Certificates + env: + MACOS_CERTIFICATE_APPLICATION: ${{ secrets.MACOS_CERTIFICATE_APPLICATION }} + MACOS_CERTIFICATE_INSTALLER: ${{ secrets.MACOS_CERTIFICATE_INSTALLER }} + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + run: | + echo "$MACOS_CERTIFICATE_APPLICATION" | base64 --decode > app_cert.p12 && chmod 600 app_cert.p12 + echo "$MACOS_CERTIFICATE_INSTALLER" | base64 --decode > installer_cert.p12 && chmod 600 installer_cert.p12 + + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + + security import app_cert.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign + security import installer_cert.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/productsign + + security set-key-partition-list -S apple-tool:,apple:,codesign:,productbuild: -s -k "$KEYCHAIN_PASSWORD" build.keychain + + rm app_cert.p12 installer_cert.p12 + - name: Build projectMSDL run: | mkdir cmake-build-frontend-sdl2 @@ -112,19 +148,162 @@ jobs: "-DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install-libprojectm;${GITHUB_WORKSPACE}/install-poco;${GITHUB_WORKSPACE}/install-libsdl2" \ "-DPRESET_DIRS=${{ github.workspace }}/presets-cream-of-the-crop" \ "-DTEXTURE_DIRS=${{ github.workspace }}/presets-milkdrop-texture-pack/textures" \ - '-DDEFAULT_CONFIG_PATH=${application.dir}/../share/projectMSDL/' \ - '-DDEFAULT_PRESETS_PATH=${application.dir}/../share/projectMSDL/presets/' \ - '-DDEFAULT_TEXTURES_PATH=${application.dir}/../share/projectMSDL/textures/' \ -DENABLE_INSTALL_BDEPS=ON cmake --build cmake-build-frontend-sdl2 --parallel + cmake --install cmake-build-frontend-sdl2 --prefix "${{ github.workspace }}/install" + + - name: Sign Application Bundle + run: | + APP_PATH="${{ github.workspace }}/install/projectM.app" + IDENTITY="Developer ID Application: Mischa Spiegelmock (5926VBQM6Y)" + ENTITLEMENTS="${{ github.workspace }}/frontend-sdl2/src/resources/projectMSDL.entitlements" + + # Sign frameworks first (SDL2, Poco, etc.) + if [ -d "$APP_PATH/Contents/Frameworks" ]; then + find "$APP_PATH/Contents/Frameworks" \( -name "*.dylib" -o -name "*.framework" \) -exec \ + codesign --force --options runtime --sign "$IDENTITY" {} \; + fi + + # Sign plugins if present + if [ -d "$APP_PATH/Contents/PlugIns" ]; then + find "$APP_PATH/Contents/PlugIns" -name "*.dylib" -exec \ + codesign --force --options runtime --sign "$IDENTITY" {} \; + fi - - name: Package projectMSDL + # Sign the main executable with entitlements + codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" \ + "$APP_PATH/Contents/MacOS/projectM" + + # Sign the entire bundle with entitlements + codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP_PATH" + + # Verify + codesign --verify --deep --strict "$APP_PATH" + + - name: Notarize Application Bundle + env: + API_KEY_BASE64: ${{ secrets.MACOS_NOTARY_API_KEY }} + API_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + API_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} run: | - cd cmake-build-frontend-sdl2 - cpack -G productbuild + mkdir -p ~/.private_keys + echo "$API_KEY_BASE64" | base64 --decode > ~/.private_keys/AuthKey_${API_KEY_ID}.p8 + chmod 600 ~/.private_keys/AuthKey_${API_KEY_ID}.p8 + + ditto -c -k --keepParent \ + "${{ github.workspace }}/install/projectM.app" \ + "projectM-notarize.zip" + + xcrun notarytool submit "projectM-notarize.zip" \ + --key ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \ + --key-id "$API_KEY_ID" \ + --issuer "$API_ISSUER_ID" \ + --wait - - name: Upload Artifact + xcrun stapler staple "${{ github.workspace }}/install/projectM.app" + + - name: Upload .app Bundle Artifact uses: actions/upload-artifact@v4 with: - name: projectMSDL-macOS-Universal - path: cmake-build-frontend-sdl2/*.pkg + name: projectMSDL-macOS-Universal-APP + path: install/ + + - name: Create projectMSDL PKG Installer + run: | + # Get version from CMake cache + VERSION=$(grep 'CMAKE_PROJECT_VERSION:STATIC=' cmake-build-frontend-sdl2/CMakeCache.txt | grep -Eo '([0-9.]+)') + + # Build component package from signed app + pkgbuild \ + --root "${{ github.workspace }}/install" \ + --identifier "org.projectm-visualizer.projectmsdl" \ + --version "$VERSION" \ + --install-location "/Applications" \ + --component-plist "frontend-sdl2/src/resources/projectMSDL-component.plist" \ + "projectMSDL-component.pkg" + + # Build unsigned product archive + productbuild \ + --distribution "frontend-sdl2/src/resources/distribution.xml" \ + --package-path "." \ + --resources "frontend-sdl2/src/resources" \ + "projectM-${VERSION}-macOS-universal-unsigned.pkg" + + # Sign the package with productsign + productsign \ + --sign "Developer ID Installer: Mischa Spiegelmock (5926VBQM6Y)" \ + "projectM-${VERSION}-macOS-universal-unsigned.pkg" \ + "projectM-${VERSION}-macOS-universal.pkg" + + rm "projectM-${VERSION}-macOS-universal-unsigned.pkg" + + - name: Notarize PKG Installer + env: + API_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + API_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + run: | + PKG_FILE=$(ls projectM-*.pkg | head -1) + + xcrun notarytool submit "$PKG_FILE" \ + --key ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \ + --key-id "$API_KEY_ID" \ + --issuer "$API_ISSUER_ID" \ + --wait + + xcrun stapler staple "$PKG_FILE" + + - name: Upload PKG Installer Artifact + uses: actions/upload-artifact@v4 + with: + name: projectMSDL-macOS-Universal-PKG + path: projectM-*.pkg + + - name: Create projectMSDL DMG Image + run: | + # Get version from CMake cache + VERSION=$(grep 'CMAKE_PROJECT_VERSION:STATIC=' cmake-build-frontend-sdl2/CMakeCache.txt | grep -Eo '([0-9.]+)') + + # Install create-dmg + brew install create-dmg + + # Build and sign DMG image + create-dmg \ + --volname "projectM Installer" \ + --volicon "frontend-sdl2/src/resources/icons/icon.icns" \ + --background "frontend-sdl2/src/resources/dmg_background.png" \ + --window-pos 200 120 \ + --window-size 800 400 \ + --icon-size 100 \ + --icon "projectM.app" 200 190 \ + --hide-extension "projectM.app" \ + --app-drop-link 600 185 \ + --codesign "Developer ID Application: Mischa Spiegelmock (5926VBQM6Y)" \ + "projectM-${VERSION}-macOS-universal.dmg" \ + "install/" + + - name: Notarize DMG Installer + env: + API_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + API_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + run: | + DMG_FILE=$(ls projectM-*.dmg | head -1) + + xcrun notarytool submit "$DMG_FILE" \ + --key ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \ + --key-id "$API_KEY_ID" \ + --issuer "$API_ISSUER_ID" \ + --wait + + xcrun stapler staple "$DMG_FILE" + + - name: Upload DMG Installer Artifact + uses: actions/upload-artifact@v4 + with: + name: projectMSDL-macOS-Universal-DMG + path: projectM-*.dmg + + - name: Cleanup Notarization API Key + if: ${{ always() }} + env: + API_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + run: rm -f ~/.private_keys/AuthKey_${API_KEY_ID}.p8 diff --git a/.github/workflows/release-windows.yaml b/.github/workflows/release-windows.yaml index cdb6f49..dddf542 100644 --- a/.github/workflows/release-windows.yaml +++ b/.github/workflows/release-windows.yaml @@ -3,7 +3,9 @@ # including projectM. name: Build Release Package for Windows -on: workflow_dispatch +on: + workflow_dispatch: + workflow_call: jobs: build: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..329a7fc --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,55 @@ +# Unified release workflow +# Triggers on version tags, builds all platforms, and creates a GitHub release +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + build-linux: + name: Build Linux + uses: ./.github/workflows/release-linux.yaml + + build-windows: + name: Build Windows + uses: ./.github/workflows/release-windows.yaml + + build-macos: + name: Build macOS + uses: ./.github/workflows/release-macos.yaml + secrets: + MACOS_CERTIFICATE_APPLICATION: ${{ secrets.MACOS_CERTIFICATE_APPLICATION }} + MACOS_CERTIFICATE_INSTALLER: ${{ secrets.MACOS_CERTIFICATE_INSTALLER }} + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + MACOS_NOTARY_API_KEY: ${{ secrets.MACOS_NOTARY_API_KEY }} + MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + + create-release: + name: Create Release + needs: [build-linux, build-windows, build-macos] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Display artifacts + run: ls -R artifacts + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: | + artifacts/**/*.pkg + artifacts/**/*.deb + artifacts/**/*.tar.gz + artifacts/**/*.zip + artifacts/**/*.msi diff --git a/packaging-macos.cmake b/packaging-macos.cmake index f155009..cb34ad9 100644 --- a/packaging-macos.cmake +++ b/packaging-macos.cmake @@ -10,8 +10,8 @@ set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/src/resources/gpl-3 set(CPACK_STRIP_FILES TRUE) ### Productbuild configuration -set(CPACK_PKGBUILD_IDENTITY_NAME "${CODESIGN_IDENTITY_INSTALLER}") -set(CPACK_PRODUCTBUILD_IDENTITY_NAME "${CODESIGN_IDENTITY_INSTALLER}") +set(CPACK_PKGBUILD_IDENTITY_NAME "$ENV{CODESIGN_IDENTITY_INSTALLER}") +set(CPACK_PRODUCTBUILD_IDENTITY_NAME "$ENV{CODESIGN_IDENTITY_INSTALLER}") set(CPACK_PRODUCTBUILD_IDENTIFIER "org.projectm-visualizer.projectmsdl") string(REPLACE ";" "," INSTALL_ARCHITECTURES "${CMAKE_OSX_ARCHITECTURES}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba764dc..4f58bdf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,6 +31,7 @@ set_target_properties(projectMSDL PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.projectm.frontend.sdl2" MACOSX_BUNDLE_ICON_FILE "projectMSDL.icns" MACOSX_BUNDLE_SHORT_VERSION_STRING "${projectMSDL_VERSION}" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist.in" ) if (CMAKE_SYSTEM_NAME STREQUAL "Windows") diff --git a/src/resources/Info.plist.in b/src/resources/Info.plist.in new file mode 100644 index 0000000..84bb6ce --- /dev/null +++ b/src/resources/Info.plist.in @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + @MACOSX_BUNDLE_EXECUTABLE_NAME@ + CFBundleIconFile + @MACOSX_BUNDLE_ICON_FILE@ + CFBundleIdentifier + @MACOSX_BUNDLE_GUI_IDENTIFIER@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @MACOSX_BUNDLE_BUNDLE_NAME@ + CFBundlePackageType + APPL + CFBundleShortVersionString + @MACOSX_BUNDLE_SHORT_VERSION_STRING@ + CFBundleVersion + @MACOSX_BUNDLE_BUNDLE_VERSION@ + NSHumanReadableCopyright + @MACOSX_BUNDLE_COPYRIGHT@ + NSHighResolutionCapable + + NSMicrophoneUsageDescription + projectM requires microphone access to visualize audio input. + + diff --git a/src/resources/distribution.xml b/src/resources/distribution.xml new file mode 100644 index 0000000..59826b8 --- /dev/null +++ b/src/resources/distribution.xml @@ -0,0 +1,17 @@ + + + projectM + org.projectm-visualizer + + + + + + + + + + + + projectMSDL-component.pkg + diff --git a/src/resources/dmg_background.png b/src/resources/dmg_background.png new file mode 100644 index 0000000..a554f39 Binary files /dev/null and b/src/resources/dmg_background.png differ diff --git a/src/resources/projectMSDL-component.plist b/src/resources/projectMSDL-component.plist index 77e470a..b33c936 100644 --- a/src/resources/projectMSDL-component.plist +++ b/src/resources/projectMSDL-component.plist @@ -12,7 +12,7 @@ BundleOverwriteAction upgrade RootRelativeBundlePath - Applications/projectM.app + projectM.app - \ No newline at end of file + diff --git a/src/resources/projectMSDL.entitlements b/src/resources/projectMSDL.entitlements new file mode 100644 index 0000000..d459cb2 --- /dev/null +++ b/src/resources/projectMSDL.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.device.audio-input + + +