From 2afd8d0988d8f8b3ca500c3fef260fc8aacd8da9 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Wed, 11 Jun 2025 12:17:04 +0200 Subject: [PATCH] Build installers for Windows ARM64 (#3825) --------- Co-authored-by: Armin Schrenk --- .github/workflows/win-exe.yml | 115 ++++++++++++++++++++++++---------- .github/workflows/winget.yml | 2 +- dist/win/build.ps1 | 71 ++++++++++++++------- 3 files changed, 133 insertions(+), 55 deletions(-) diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index c1f0f7679..c31af3fff 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -21,8 +21,6 @@ on: env: - JAVA_DIST: 'zulu' - JAVA_VERSION: '24.0.1+9' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_windows-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64_HASH: 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f' WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi' @@ -41,8 +39,21 @@ jobs: build-msi: name: Build .msi Installer - runs-on: windows-latest - needs: [get-version] + runs-on: ${{ matrix.os }} + needs: [ get-version ] + strategy: + matrix: + include: + - arch: x64 + os: windows-latest + java-dist: 'zulu' + java-version: '24.0.1+9' + java-package: 'jdk' + - arch: arm64 + os: windows-11-arm + java-dist: 'liberica' + java-version: '24.0.1+11' + java-package: 'jdk+fx' #This is needed, as liberica contains JFX 24 Jmods for Windows ARM64 env: LOOPBACK_ALIAS: 'cryptomator-vault' WIN_CONSOLE_FLAG: '' @@ -51,8 +62,9 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - distribution: ${{ env.JAVA_DIST }} - java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ matrix.java-dist }} + java-version: ${{ matrix.java-version }} + java-package: ${{ matrix.java-package }} check-latest: true cache: 'maven' - name: Install wix and extensions @@ -61,6 +73,7 @@ jobs: wix.exe extension add WixToolset.UI.wixext/6.0.0 --global wix.exe extension add WixToolset.Util.wixext/6.0.0 --global - name: Download and extract JavaFX jmods from Gluon + if: matrix.arch == 'x64' #In the last step we move all jmods files a dir level up because jmods are placed inside a directory in the zip run: | curl --output openjfx-jmods.zip -L "${{ env.OPENJFX_JMODS_AMD64 }}" @@ -71,6 +84,7 @@ jobs: Get-ChildItem -Path openjfx-jmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent} shell: pwsh - name: Ensure major jfx version in pom and in jmods is the same + if: matrix.arch == 'x64' run: | JMOD_VERSION_AMD64=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64#*@} @@ -84,7 +98,7 @@ jobs: exit 1 fi - name: Set version - run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} + run: mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} - name: Run maven run: mvn -B clean package -Pwin -DskipTests -Djavafx.platform=win - name: Patch target dir @@ -100,7 +114,7 @@ jobs: fi echo "jmod_paths=${JMOD_PATHS}" >> "$GITHUB_OUTPUT" - name: Run jlink - #Remark: no compression is applied for improved build compression later (here msi) + # Remark: no compression is applied for improved build compression later (here msi) run: > ${JAVA_HOME}/bin/jlink --verbose @@ -258,8 +272,8 @@ jobs: description: Cryptomator Installer timestampUrl: 'http://timestamp.digicert.com' folder: installer - - name: Add possible alpha/beta tags to installer name - run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.msi + - name: Add possible alpha/beta tags and architecture to installer name + run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.arch }}.msi - name: Create detached GPG signature with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import @@ -270,7 +284,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: msi + name: msi-${{ matrix.arch }} path: | Cryptomator-*.msi Cryptomator-*.asc @@ -278,8 +292,23 @@ jobs: build-exe: name: Build .exe installer - runs-on: windows-latest - needs: [get-version, build-msi] + runs-on: ${{ matrix.os }} + needs: [ get-version, build-msi ] + strategy: + matrix: + include: + - arch: x64 + os: windows-latest + executable-suffix: x64 + java-dist: 'zulu' + java-version: '24.0.1+9' + java-package: 'jdk' + - arch: arm64 + os: windows-11-arm + executable-suffix: arm64 + java-dist: 'liberica' + java-version: '24.0.1+11' + java-package: 'jdk+fx' #This is needed, as liberica contains JFX 24 Jmods for Windows ARM64 steps: - uses: actions/checkout@v4 - name: Install wix and extensions @@ -290,14 +319,16 @@ jobs: - name: Download .msi uses: actions/download-artifact@v4 with: - name: msi + name: msi-${{ matrix.arch }} path: dist/win/bundle/resources - name: Strip version info from msi file name run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi - - uses: actions/setup-java@v4 + - name: Setup Java + uses: actions/setup-java@v4 with: - distribution: ${{ env.JAVA_DIST }} - java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ matrix.java-dist }} + java-version: ${{ matrix.java-version }} + java-package: ${{ matrix.java-package }} check-latest: true cache: 'maven' - name: Generate license for exe @@ -365,7 +396,7 @@ jobs: timestampUrl: 'http://timestamp.digicert.com' folder: installer - name: Add possible alpha/beta tags to installer name - run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.exe + run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.executable-suffix }}.exe - name: Create detached GPG signature with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import @@ -376,7 +407,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: exe + name: exe-${{ matrix.executable-suffix }} path: | Cryptomator-*.exe Cryptomator-*.asc @@ -386,16 +417,18 @@ jobs: name: Publish installers to the github release if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' runs-on: ubuntu-latest - needs: [build-msi, build-exe] + needs: [ build-msi, build-exe ] outputs: - download-url-msi: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }} - download-url-exe: ${{ fromJSON(steps.publish.outputs.assets)[1].browser_download_url }} + download-url-msi-x64: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }} + download-url-msi-arm64: ${{ fromJSON(steps.publish.outputs.assets)[1].browser_download_url }} + download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }} + download-url-exe-arm64: ${{ fromJSON(steps.publish.outputs.assets)[3].browser_download_url }} steps: - name: Download installers uses: actions/download-artifact@v4 with: merge-multiple: true - - name: Publish .msi on GitHub Releases + - name: Publish installers on GitHub Releases id: publish uses: softprops/action-gh-release@v2 with: @@ -403,22 +436,38 @@ jobs: token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} # do not change ordering of filelist, required for correct job output files: | - *.msi - *.exe + *x64.msi + *arm64.msi + *x64.exe + *arm64.exe *.asc - allowlist-msi: + allowlist-msi-x64: uses: ./.github/workflows/av-whitelist.yml - needs: [publish] + needs: [ publish ] with: - url: ${{ needs.publish.outputs.download-url-msi }} + url: ${{ needs.publish.outputs.download-url-msi-x64 }} secrets: inherit - allowlist-exe: + allowlist-msi-arm64: uses: ./.github/workflows/av-whitelist.yml - needs: [publish, allowlist-msi] + needs: [ publish ] with: - url: ${{ needs.publish.outputs.download-url-exe }} + url: ${{ needs.publish.outputs.download-url-msi-arm64 }} + secrets: inherit + + allowlist-exe-x64: + uses: ./.github/workflows/av-whitelist.yml + needs: [ publish, allowlist-msi-x64 ] + with: + url: ${{ needs.publish.outputs.download-url-exe-x64 }} + secrets: inherit + + allowlist-exe-arm64: + uses: ./.github/workflows/av-whitelist.yml + needs: [ publish, allowlist-msi-arm64 ] + with: + url: ${{ needs.publish.outputs.download-url-exe-arm64 }} secrets: inherit notify-winget: @@ -435,7 +484,7 @@ jobs: SLACK_ICON: false SLACK_ICON_EMOJI: ':bot:' SLACK_CHANNEL: 'cryptomator-desktop' - SLACK_TITLE: "MSI of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published." - SLACK_MESSAGE: "Ready to ." + SLACK_TITLE: "MSI packages of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published." + SLACK_MESSAGE: "Ready to ." SLACK_FOOTER: false MSG_MINIMAL: true diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 6d5a9c57d..476e409e3 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -23,5 +23,5 @@ jobs: identifier: Cryptomator.Cryptomator version: ${{ inputs.tag }} release-tag: ${{ inputs.tag }} - installers-regex: '\.msi$' + installers-regex: '-x64\.msi$' token: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }} \ No newline at end of file diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index ce19ee6a8..a861cf40d 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -33,15 +33,15 @@ if ((Get-Command 'wix' -ErrorAction SilentlyContinue) -eq $null) } $wixExtensions = & wix.exe extension list --global | Out-String if ($wixExtensions -notmatch 'WixToolset.UI.wixext') { - Write-Error 'UI wix extension missing. Please install it with: wix.exe extension add WixToolset.UI.wixext/6.0.0 --global)' + Write-Error 'Wix UI extension missing. Please install it with: wix.exe extension add WixToolset.UI.wixext/6.0.0 --global)' exit 1 } if ($wixExtensions -notmatch 'WixToolset.Util.wixext') { - Write-Error 'Util wix extension missing. Please install it with: wix.exe extension add WixToolset.Util.wixext/6.0.0 --global)' + Write-Error 'Wix Util extension missing. Please install it with: wix.exe extension add WixToolset.Util.wixext/6.0.0 --global)' exit 1 } if ($wixExtensions -notmatch 'WixToolset.BootstrapperApplications.wixext') { - Write-Error 'Util wix extension missing. Please install it with: wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global)' + Write-Error 'Wix Bootstrapper extension missing. Please install it with: wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global)' exit 1 } @@ -68,32 +68,60 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) { Remove-Item -Path $runtimeImagePath -Force -Recurse } -## download jfx jmods -$javaFxVersion='24.0.1' -$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip" -$javaFxJmodsSHA256 = 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f' -$javaFxJmods = '.\resources\jfxJmods.zip' -if( !(Test-Path -Path $javaFxJmods) ) { - Write-Output "Downloading ${javaFxJmodsUrl}..." - Invoke-WebRequest $javaFxJmodsUrl -OutFile $javaFxJmods # redirects are followed by default +## download jfx jmods for X64, while they are part of the Arm64 JDK +$archCode = (Get-CimInstance Win32_Processor).Architecture +$archName = switch ($archCode) { + 9 { "x64 (AMD64)" } + 12 { "ARM64" } + default { "WMI Win32_Processor.Architecture code ($archCode)" } } -$jmodsChecksumActual = $(Get-FileHash -Path $javaFxJmods -Algorithm SHA256).Hash.ToLower() -if( $jmodsChecksumActual -ne $javaFxJmodsSHA256 ) { - Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $javaFxJmodsSHA256 -, actual: $jmodsChecksumActual" - exit 1; +switch ($archName) { + 'ARM64' { + $javafxBaseJmod = Join-Path $Env:JAVA_HOME "jmods\javafx.base.jmod" + if (!(Test-Path $javafxBaseJmod)) { + Write-Error "JavaFX module not found in JDK. Please ensure full JDK (including jmods) is installed." + exit 1 + } + + $jmodPaths = "$Env:JAVA_HOME/jmods" + } + 'x64 (AMD64)' { + $javaFxVersion='24.0.1' + $javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip" + $javaFxJmodsSHA256 = 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f' + $javaFxJmods = '.\resources\jfxJmods.zip' + + if( !(Test-Path -Path $javaFxJmods) ) { + Write-Output "Downloading ${javaFxJmodsUrl}..." + Invoke-WebRequest $javaFxJmodsUrl -OutFile $javaFxJmods # redirects are followed by default + } + + $jmodsChecksumActual = $(Get-FileHash -Path $javaFxJmods -Algorithm SHA256).Hash.ToLower() + if( $jmodsChecksumActual -ne $javaFxJmodsSHA256 ) { + Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $javaFxJmodsSHA256 + , actual: $jmodsChecksumActual" + exit 1; + } + + Expand-Archive -Path $javaFxJmods -Force -DestinationPath ".\resources\" + Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods" -ErrorAction Ignore + Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop + + $jmodPaths="$buildDir/resources/javafx-jmods"; + } + default { + Write-Error "Unsupported architecture: $arch" + exit 1 + } } -Expand-Archive -Path $javaFxJmods -Force -DestinationPath ".\resources\" -Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods" -ErrorAction Ignore -Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop ## create custom runtime ### check for JEP 493 -$jmodPaths="$buildDir/resources/javafx-jmods"; if ((& "$Env:JAVA_HOME\bin\jlink" --help | Select-String -Pattern "Linking from run-time image enabled" -SimpleMatch | Measure-Object).Count -eq 0 ) { $jmodPaths="$Env:JAVA_HOME/jmods;" + $jmodPaths; } + ### create runtime & "$Env:JAVA_HOME\bin\jlink" ` --verbose ` @@ -196,7 +224,6 @@ if ($LASTEXITCODE -ne 0) { return 1; } - #Create RTF license for bundle &mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" ` "-Dlicense.thirdPartyFilename=license.rtf" ` @@ -243,3 +270,5 @@ Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName -ext "WixToolset.BootstrapperApplications.wixext" ` .\bundle\bundleWithWinfsp.wxs ` -out "installer\$AppName-Installer.exe" + +Write-Output "Created EXE installer .\installer\$AppName-Installer.exe"