name: Build on: push: branches: - dev - master pull_request: branches: - dev - master env: CICD_REPO_SLUG: jindrapetrik/jpexs-decompiler GITHUB_USER: jindrapetrik GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} permissions: contents: write id-token: write jobs: compute-version: name: Compute version runs-on: windows-latest concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true outputs: doRelease: ${{ steps.vars.outputs.doRelease }} verMajor: ${{ steps.vars.outputs.verMajor }} verMinor: ${{ steps.vars.outputs.verMinor }} verRelease: ${{ steps.vars.outputs.verRelease }} verBuild: ${{ steps.vars.outputs.verBuild }} verRevision: ${{ steps.vars.outputs.verRevision }} verLong: ${{ steps.vars.outputs.verLong }} verRaw: ${{ steps.vars.outputs.verRaw }} verShort: ${{ steps.vars.outputs.verShort }} verDebug: ${{ steps.vars.outputs.verDebug }} verOldTag: ${{ steps.vars.outputs.verOldTag }} verTag: ${{ steps.vars.outputs.verTag }} verTitle: ${{ steps.vars.outputs.verTitle }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up JDK uses: actions/setup-java@v4 with: distribution: adopt architecture: x64 java-version: 8 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.3' - name: Compute variables id: vars shell: pwsh run: | Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # Ensure tags exist locally git fetch --tags --force | Out-Null $ref = "${env:GITHUB_REF}" # e.g. refs/tags/version1.2.3 or refs/heads/dev $eventType = "${env:GITHUB_EVENT_NAME}" # e.g. "push" or "pull_request" $isTag = $ref.StartsWith("refs/tags/") $isDev = ($ref -eq "refs/heads/dev") $isMaster = ($ref -eq "refs/heads/master") $isPush = ($eventType -eq "push") $H = (git rev-parse HEAD).Trim() function Set-Var([string]$name, [string]$value) { # For later steps in the SAME job "$name=$value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # For later jobs (job outputs) "$name=$value" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append } # Defaults Set-Var "doRelease" "false" Set-Var "verMajor" "" Set-Var "verMinor" "" Set-Var "verRelease" "" Set-Var "verBuild" "" Set-Var "verRevision" "" Set-Var "verLong" "" Set-Var "verRaw" "" Set-Var "verShort" "" Set-Var "verDebug" "" Set-Var "verOldTag" "" Set-Var "verTag" "" Set-Var "verTitle" "" $raw = ""; if ($isMaster) { $raw = php cicd_scripts/update_changelog.php true } if ($isDev -and $isPush) { $raw = php cicd_scripts/update_changelog.php false } if ($isMaster -or ($isDev -and $isPush)) { mkdir build/classes -Force javac -cp build/classes -d build/classes src/com/jpexs/build/ChangelogUpdater.java java -cp build/classes com.jpexs.build.ChangelogUpdater } if ($isMaster -and ($raw -match '^(\d+)\.(\d+)\.(\d+)$')) { $X = $Matches[1] $Y = $Matches[2] $Z = $Matches[3] Set-Var "verMajor" $X Set-Var "verMinor" $Y Set-Var "verRelease" $Z Set-Var "verBuild" "0" Set-Var "verRevision" $H Set-Var "verRaw" $raw Set-Var "verShort" $raw Set-Var "verLong" $raw Set-Var "verDebug" "false" Set-Var "verOldTag" "" Set-Var "verTag" "version$raw" Set-Var "doRelease" "true" Set-Var "verTitle" "version $raw" exit 0 } # Case 2: dev branch and push if ($isDev -and $isPush -and ($raw -eq "nightly")) { # Find latest nightlyN tag (max N) $nightlyTags = git tag --list 'nightly*' $maxN = -1 foreach ($t in $nightlyTags) { if ($t -match '^nightly(\d+)$') { $n = [int]$Matches[1] if ($n -gt $maxN) { $maxN = $n } } } if ($maxN -lt 0) { $maxN = 0 } # if none exists, start from 0 so M becomes 1 $M = $maxN + 1 # Find latest versionX.Y.Z tag by numeric comparison (major,minor,release) $versionTags = git tag --list 'version*' $best = $null foreach ($t in $versionTags) { if ($t -match '^version(\d+)\.(\d+)\.(\d+)$') { $maj = [int]$Matches[1] $min = [int]$Matches[2] $rel = [int]$Matches[3] $obj = [pscustomobject]@{ Tag=$t; Major=$maj; Minor=$min; Release=$rel } if (-not $best) { $best = $obj } else { if ($obj.Major -gt $best.Major -or ($obj.Major -eq $best.Major -and $obj.Minor -gt $best.Minor) -or ($obj.Major -eq $best.Major -and $obj.Minor -eq $best.Minor -and $obj.Release -gt $best.Release)) { $best = $obj } } } } if (-not $best) { throw "No versionX.Y.Z tag found. Create at least one version tag (e.g. version1.0.0)." } $X = $best.Major $Y = $best.Minor $Z = $best.Release Set-Var "verMajor" "$X" Set-Var "verMinor" "$Y" Set-Var "verRelease" "$Z" Set-Var "verBuild" "$M" Set-Var "verRevision" $H Set-Var "verLong" "$X.$Y.$Z nightly build $M" Set-Var "verRaw" "$X.$Y.$Z" Set-Var "verShort" "$X.$Y.$Z`_nightly$M" Set-Var "verDebug" "true" Set-Var "verOldTag" "nightly$maxN" Set-Var "verTag" "nightly$M" Set-Var "verTitle" "(PREVIEW) version $X.$Y.$Z nightly $M" Set-Var "doRelease" "true" exit 0 } # Other cases: doRelease stays false exit 0 - name: Prepare version info property file shell: pwsh run: | $VERSION_PROP_FILE = "version.properties" echo "">$VERSION_PROP_FILE echo "major=${{ steps.vars.outputs.verMajor }}">>$VERSION_PROP_FILE echo "minor=${{ steps.vars.outputs.verMinor }}">>$VERSION_PROP_FILE echo "release=${{ steps.vars.outputs.verRelease }}">>$VERSION_PROP_FILE echo "build=${{ steps.vars.outputs.verBuild }}">>$VERSION_PROP_FILE echo "revision=${{ steps.vars.outputs.verRevision }}">>$VERSION_PROP_FILE echo "debug=${{ steps.vars.outputs.verDebug }}">>$VERSION_PROP_FILE - name: Upload CHANGELOG.md artifact uses: actions/upload-artifact@v4 if: steps.vars.outputs.doRelease == 'true' with: name: changelog_md path: CHANGELOG.md - name: Upload version.properties artifact uses: actions/upload-artifact@v4 with: name: version_properties path: version.properties build: name: Build and test runs-on: windows-latest concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true needs: compute-version steps: - name: Checkout uses: actions/checkout@v3 - name: Set up JDK uses: actions/setup-java@v4 with: distribution: adopt architecture: x64 java-version: | 8 21 - name: Set Java 8 shell: pwsh run: | echo "JAVA_HOME=$env:JAVA_HOME_8_X64" >> $env:GITHUB_ENV - name: Set up Ant run: choco install ant -y - name: Download version.properties artifact uses: actions/download-artifact@v4 with: name: version_properties - name: Copy version.properties to lib run: copy version.properties libsrc/ffdec_lib/ - name: Check style run: ant checkstyle - name: Build and Test (Release) if: needs.compute-version.outputs.doRelease == 'true' run: ant new-version-set build test - name: Build and Test (Private) if: needs.compute-version.outputs.doRelease == 'false' run: ant build test - name: Set Java 21 if: needs.compute-version.outputs.doRelease == 'true' shell: pwsh run: | echo "JAVA_HOME=$env:JAVA_HOME_21_X64" >> $env:GITHUB_ENV - name: Javadoc if: needs.compute-version.outputs.doRelease == 'true' run: ant release_lib_javadoc - name: Upload lib javadoc artifact if: needs.compute-version.outputs.doRelease == 'true' uses: actions/upload-artifact@v4 with: name: lib_javadoc path: releases/ffdec_lib_javadoc_${{ needs.compute-version.outputs.verShort }}.zip - name: Upload dist uses: actions/upload-artifact@v4 with: name: dist path: dist/ - name: Upload lib dist uses: actions/upload-artifact@v4 with: name: lib_dist path: libsrc/ffdec_lib/dist exe: name: Generate EXEs runs-on: ubuntu-latest concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true needs: - compute-version - build if: needs.compute-version.outputs.doRelease == 'true' steps: - name: Checkout uses: actions/checkout@v3 - name: Set up JDK uses: actions/setup-java@v4 with: distribution: adopt architecture: x64 java-version: 8 # Wine must be used since Windows runner cannot run Resource hacker - name: Install Wine run: | sudo dpkg --add-architecture i386 sudo apt-get update -y -qq sudo apt-get install -y -qq wine # We need to add fake virtual desktop as Resource Hacker is not pure commandline app - name: Set up Xorg run: | sudo apt-get install -y -qq xorg xvfb xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic - name: Install Resource Hacker env: RH_DIR: tools/wine/resourcehacker RH_ZIP: tools/wine/resourcehacker.zip RH_URL: https://www.free-decompiler.com/flash/tools/resource_hacker.zip #https://www.angusj.com/resourcehacker/resource_hacker.zip run: | set -euxo pipefail sudo apt-get install -y -qq unzip wget mkdir -p "$(dirname "$RH_ZIP")" mkdir -p "$RH_DIR" wget -q -O "$RH_ZIP" "$RH_URL" unzip -o "$RH_ZIP" -d "$RH_DIR" - name: Generate EXE splash screen run: | mkdir -p build/classes javac -d build/classes libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/BMPFile.java javac -cp build/classes -d build/classes src/com/jpexs/build/SplashScreenGenerator.java java -cp build/classes com.jpexs.build.SplashScreenGenerator "${{ needs.compute-version.outputs.verLong }}" - name: Replace EXE splash screen run: | export WINEPREFIX=$(pwd)/tools/wine cp resources/ffdec.exe $WINEPREFIX/ffdec.exe cp build/splash.bmp $WINEPREFIX/splash.bmp xvfb-run wine $WINEPREFIX/resourcehacker/ResourceHacker.exe -open $WINEPREFIX/ffdec.exe -save $WINEPREFIX/ffdec.exe -action addoverwrite -res $WINEPREFIX/splash.bmp -mask BITMAP,1 cp $WINEPREFIX/ffdec.exe resources/ffdec.exe - name: Generate EXE version info run: | sed -i \ -e 's/@MAJOR@/${{ needs.compute-version.outputs.verMajor }}/g' \ -e 's/@MINOR@/${{ needs.compute-version.outputs.verMinor }}/g' \ -e 's/@RELEASE@/${{ needs.compute-version.outputs.verRelease }}/g' \ -e 's/@BUILD@/${{ needs.compute-version.outputs.verBuild }}/g' \ -e 's/@VERSION@/${{ needs.compute-version.outputs.verShort }}/g' \ versioninfo.rc - name: Replace EXE version info run: | export WINEPREFIX=$(pwd)/tools/wine cp resources/ffdec.exe $WINEPREFIX/ffdec.exe cp versioninfo.rc $WINEPREFIX/versioninfo.rc xvfb-run wine $WINEPREFIX/resourcehacker/ResourceHacker.exe -open $WINEPREFIX/versioninfo.rc -save $WINEPREFIX/versioninfo.res -action compile xvfb-run wine $WINEPREFIX/resourcehacker/ResourceHacker.exe -open $WINEPREFIX/ffdec.exe -save $WINEPREFIX/ffdec.exe -action addoverwrite -res $WINEPREFIX/versioninfo.res -mask "Version Info,1" cp $WINEPREFIX/ffdec.exe resources/ffdec.exe - name: Upload EXE uses: actions/upload-artifact@v4 with: name: unsigned_exe path: resources/ffdec.exe - name: Generate CLI EXE version info run: | sed -i \ -e 's/@MAJOR@/${{ needs.compute-version.outputs.verMajor }}/g' \ -e 's/@MINOR@/${{ needs.compute-version.outputs.verMinor }}/g' \ -e 's/@RELEASE@/${{ needs.compute-version.outputs.verRelease }}/g' \ -e 's/@BUILD@/${{ needs.compute-version.outputs.verBuild }}/g' \ -e 's/@VERSION@/${{ needs.compute-version.outputs.verShort }}/g' \ versioninfo-cli.rc - name: Replace CLI EXE version info run: | export WINEPREFIX=$(pwd)/tools/wine cp resources/ffdec-cli.exe $WINEPREFIX/ffdec-cli.exe cp versioninfo-cli.rc $WINEPREFIX/versioninfo-cli.rc xvfb-run wine $WINEPREFIX/resourcehacker/ResourceHacker.exe -open $WINEPREFIX/versioninfo-cli.rc -save $WINEPREFIX/versioninfo-cli.res -action compile xvfb-run wine $WINEPREFIX/resourcehacker/ResourceHacker.exe -open $WINEPREFIX/ffdec-cli.exe -save $WINEPREFIX/ffdec-cli.exe -action addoverwrite -res $WINEPREFIX/versioninfo-cli.res -mask "Version Info,1" cp $WINEPREFIX/ffdec-cli.exe resources/ffdec-cli.exe - name: Upload CLI EXE uses: actions/upload-artifact@v4 with: name: unsigned_cli_exe path: resources/ffdec-cli.exe #--- Note: Windows runner cannot run Resource Hacker for some reason # exe: # name: Generate EXE # runs-on: windows-latest # needs: compute-version # if: needs.compute-version.outputs.doRelease == 'true' # steps: # - name: Checkout # uses: actions/checkout@v3 # - name: Set up JDK # uses: actions/setup-java@v4 # with: # distribution: adopt # architecture: x64 # java-version: 8 # # - name: Set up ResourceHacker # id: resource_hacker # shell: pwsh # run: | # $url = "https://www.angusj.com/resourcehacker/resource_hacker.zip" # $zip = "resource_hacker.zip" # $dest = "tools/resource_hacker" # # Invoke-WebRequest $url -OutFile $zip # Expand-Archive $zip -DestinationPath $dest -Force # $path = $dest + '/ResourceHacker.exe' # "path=$path" | Out-File -FilePath $env:GITHUB_OUTPUT -Append # # - name: Generate EXE splash screen # shell: pwsh # run: | # mkdir build/classes # javac -d build/classes libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/BMPFile.java # javac -cp build/classes -d build/classes src/com/jpexs/build/SplashScreenGenerator.java # java -cp build/classes com.jpexs.build.SplashScreenGenerator "${{ needs.compute-version.outputs.verLong }}" # # - name: Replace EXE splash screen # shell: pwsh # run: | # $resource_hacker = "${{ steps.resource_hacker.outputs.path }}" # # & $resource_hacker -open resources/ffdec.exe -save resources/ffdec.exe -action addoverwrite -res build/splash.bmp -mask BITMAP,1 # - name: Generate EXE version info # shell: pwsh # run: | # $template = Get-Content -Path 'versioninfo.rc' -Raw -Encoding UTF8 # $t = $template # $t = $t -creplace '@MAJOR@', '${{ needs.compute-version.outputs.verMajor }}' # $t = $t -creplace '@MINOR@', '${{ needs.compute-version.outputs.verMinor }}' # $t = $t -creplace '@RELEASE@', '${{ needs.compute-version.outputs.verRelease }}' # $t = $t -creplace '@BUILD@', '${{ needs.compute-version.outputs.verBuild }}' # $t = $t -creplace '@VERSION@', '${{ needs.compute-version.outputs.verShort }}' # Set-Content -Path 'versioninfo.rc' -Value $t -Encoding UTF8 # - name: Replace EXE version info # shell: pwsh # run: | # $resource_hacker = "${{ steps.resource_hacker.outputs.path }}" # & $resource_hacker -open versioninfo.rc -save versioninfo.res -action compile # & $resource_hacker -open resources/ffdec.exe -save resources/ffdec.exe -action addoverwrite -res versioninfo.res -mask "Version Info,1" # - name: Upload EXE # uses: actions/upload-artifact@v4 # with: # name: unsigned_exe # path: resources/ffdec.exe sign_and_msi: name: Code signing, MSI installer runs-on: windows-latest concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true needs: - compute-version - build - exe if: needs.compute-version.outputs.doRelease == 'true' env: GCP_PROJECT_ID: jpexs-ffdec GCP_LOCATION: europe KMS_KEYRING: jpexs-ffdec-keyring KMS_KEY: jpexs-ffdec-key2 KMS_KEY_VERSION: "1" CERT_PATH: "cert/user.crt" GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout uses: actions/checkout@v4 - name: Download dist artifact uses: actions/download-artifact@v4 with: name: dist path: dist/ - name: Download lib_dist artifact uses: actions/download-artifact@v4 with: name: lib_dist path: libsrc/ffdec_lib/dist/ - name: Download unsigned EXE artifact uses: actions/download-artifact@v4 with: name: unsigned_exe path: dist/ - name: Download unsigned CLI EXE artifact uses: actions/download-artifact@v4 with: name: unsigned_cli_exe path: dist/ - name: Set up JDK uses: actions/setup-java@v4 with: distribution: adopt architecture: x64 java-version: 23 - name: Build alt signer working-directory: altsigner run: mvn clean package - id: auth uses: google-github-actions/auth@v2 with: workload_identity_provider: "projects/541721778634/locations/global/workloadIdentityPools/github-pool/providers/github-provider" service_account: "gha-codesign@jpexs-ffdec.iam.gserviceaccount.com" - name: Setup gcloud uses: google-github-actions/setup-gcloud@v3 with: project_id: "${{ env.GCP_PROJECT_ID }}" - name: Install Google Cloud KMS CNG Provider shell: pwsh env: GH_TOKEN: ${{secrets.GH_TOKEN}} run: | $repo = "GoogleCloudPlatform/kms-integrations" $tmp = "$env:RUNNER_TEMP\kms" New-Item -ItemType Directory -Force -Path $tmp | Out-Null $tag = gh release list ` --repo $repo ` --limit 50 ` --json tagName,createdAt ` -q '[.[] | select(.tagName | startswith("cng-"))][0].tagName' if (-not $tag) { throw "No release found with tag cng-*" } Write-Host "Using release tag: $tag" gh release download $tag ` --repo $repo ` --pattern "*windows-amd64*.zip" ` --dir $tmp $zip = Get-ChildItem $tmp -Filter "*.zip" | Select-Object -First 1 if (-not $zip) { throw "ZIP asset not found (pattern *windows-amd64*.zip). Check names in releases." } $extract = Join-Path $tmp "extract" New-Item -ItemType Directory -Force -Path $extract | Out-Null Expand-Archive -Path $zip.FullName -DestinationPath $extract -Force $msi = Get-ChildItem $extract -Recurse -Filter "*.msi" | Select-Object -First 1 if (-not $msi) { throw "MSI not found inside ZIP. Check structure of archive in releases." } Write-Host "Installing: $($msi.FullName)" Start-Process msiexec.exe -Wait -ArgumentList "/i `"$($msi.FullName)`" /qn /norestart" - name: Sign ffdec.jar shell: pwsh run: | $kc = "projects/$env:GCP_PROJECT_ID/locations/$env:GCP_LOCATION/keyRings/$env:KMS_KEYRING/cryptoKeys/$env:KMS_KEY/cryptoKeyVersions/$env:KMS_KEY_VERSION" java -cp altsigner\target\kms-jarsigner-1.0.jar com.jpexs.kmsjarsigner.SignJar dist/ffdec.jar dist/ffdec-signed.jar cert/cert-chain.pem $kc http://timestamp.sectigo.com move dist/ffdec-signed.jar dist/ffdec.jar -Force - name: Verify ffdec.jar signature shell: pwsh run: jarsigner.exe -verify -strict dist/ffdec.jar - name: Upload signed JAR artifact uses: actions/upload-artifact@v4 with: name: signed_jar path: dist/ffdec.jar - name: Sign ffdec_lib.jar shell: pwsh run: | $kc = "projects/$env:GCP_PROJECT_ID/locations/$env:GCP_LOCATION/keyRings/$env:KMS_KEYRING/cryptoKeys/$env:KMS_KEY/cryptoKeyVersions/$env:KMS_KEY_VERSION" java -cp altsigner\target\kms-jarsigner-1.0.jar com.jpexs.kmsjarsigner.SignJar libsrc/ffdec_lib/dist/ffdec_lib.jar libsrc/ffdec_lib/dist/ffdec_lib-signed.jar cert/cert-chain.pem $kc http://timestamp.sectigo.com move libsrc/ffdec_lib/dist/ffdec_lib-signed.jar libsrc/ffdec_lib/dist/ffdec_lib.jar -Force - name: Verify ffdec_lib.jar signature shell: pwsh run: jarsigner.exe -verify -strict libsrc/ffdec_lib/dist/ffdec_lib.jar - name: Upload signed lib JAR artifact uses: actions/upload-artifact@v4 with: name: signed_lib_jar path: libsrc/ffdec_lib/dist/ffdec_lib.jar - name: Locate signtool id: signtool shell: pwsh run: | $candidates = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" ` -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue Write-Host "All signtool candidates:" $candidates | ForEach-Object { Write-Host " - $($_.FullName)" } # vyber jen x64 $x64 = $candidates | Where-Object { $_.FullName -match "\\x64\\" } if (-not $x64) { throw "No x64 signtool.exe found" } $path = $x64 | Sort-Object FullName -Descending | Select-Object -First 1 "path=$path" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - name: Sign EXE with KMS key shell: pwsh run: | $signtool = "${{ steps.signtool.outputs.path }}" $kc = "projects/$env:GCP_PROJECT_ID/locations/$env:GCP_LOCATION/keyRings/$env:KMS_KEYRING/cryptoKeys/$env:KMS_KEY/cryptoKeyVersions/$env:KMS_KEY_VERSION" $ErrorActionPreference = 'Stop' $exe = $signtool $args = @( "sign", "/v", "/debug", "/fd", "sha256", "/tr", "http://timestamp.sectigo.com?td=sha256", "/td", "sha256", "/f", "$env:CERT_PATH", "/csp", "Google Cloud KMS Provider", "/kc", "$kc", "dist/ffdec.exe" ) # --- retry policy --- $maxAttempts = 5 $delaySeconds = 10 $needle = "SignTool Error: An unexpected internal error has occurred" for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { Write-Host "Attempt $attempt/${maxAttempts}: $exe $($args -join ' ')" $output = & $exe @args 2>&1 | Out-String $exitCode = $LASTEXITCODE if ($output) { Write-Host $output.TrimEnd() } if ($exitCode -eq 0) { Write-Host "Succeeded." exit 0 } $hasNeedle = $output -match [regex]::Escape($needle) if ($hasNeedle -and $attempt -lt $maxAttempts) { Write-Warning "Detected transient SignTool internal error. Retrying in $delaySeconds seconds..." Start-Sleep -Seconds $delaySeconds continue } if ($hasNeedle) { throw "Failed after $maxAttempts attempts due to repeated SignTool internal error (exit code $exitCode)." } else { throw "Command failed (exit code $exitCode). Output did not match retry condition." } } - name: Verify EXE signature shell: pwsh run: | $signtool = "${{ steps.signtool.outputs.path }}" & $signtool verify /pa /v "dist/ffdec.exe" - name: Sign CLI EXE with KMS key shell: pwsh run: | $signtool = "${{ steps.signtool.outputs.path }}" $kc = "projects/$env:GCP_PROJECT_ID/locations/$env:GCP_LOCATION/keyRings/$env:KMS_KEYRING/cryptoKeys/$env:KMS_KEY/cryptoKeyVersions/$env:KMS_KEY_VERSION" $ErrorActionPreference = 'Stop' $exe = $signtool $args = @( "sign", "/v", "/debug", "/fd", "sha256", "/tr", "http://timestamp.sectigo.com?td=sha256", "/td", "sha256", "/f", "$env:CERT_PATH", "/csp", "Google Cloud KMS Provider", "/kc", "$kc", "dist/ffdec-cli.exe" ) # --- retry policy --- $maxAttempts = 5 $delaySeconds = 10 $needle = "SignTool Error: An unexpected internal error has occurred" for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { Write-Host "Attempt $attempt/${maxAttempts}: $exe $($args -join ' ')" $output = & $exe @args 2>&1 | Out-String $exitCode = $LASTEXITCODE if ($output) { Write-Host $output.TrimEnd() } if ($exitCode -eq 0) { Write-Host "Succeeded." exit 0 } $hasNeedle = $output -match [regex]::Escape($needle) if ($hasNeedle -and $attempt -lt $maxAttempts) { Write-Warning "Detected transient SignTool internal error. Retrying in $delaySeconds seconds..." Start-Sleep -Seconds $delaySeconds continue } if ($hasNeedle) { throw "Failed after $maxAttempts attempts due to repeated SignTool internal error (exit code $exitCode)." } else { throw "Command failed (exit code $exitCode). Output did not match retry condition." } } - name: Verify CLI EXE signature shell: pwsh run: | $signtool = "${{ steps.signtool.outputs.path }}" & $signtool verify /pa /v "dist/ffdec-cli.exe" - name: Get Msi tools path id: msitools shell: pwsh run: | $candidates = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" ` -Recurse -Filter MsiTran.exe -ErrorAction SilentlyContinue Write-Host "All signtool candidates:" $candidates | ForEach-Object { Write-Host " - $($_.FullName)" } $x86 = $candidates | Where-Object { $_.FullName -match "\\x86\\" } if (-not $x86) { throw "No x86 MsiTran.exe found" } $path = $x86 | Sort-Object FullName -Descending | Select-Object -First 1 $path = Split-Path $path -Parent "path=$path" | Out-File -FilePath $env:GITHUB_OUTPUT -Append #Add-Content $env:GITHUB_PATH $path - name: Inject version info shell: pwsh run: | $template = Get-Content -Path 'wix\Product.wxs' -Raw -Encoding UTF8 $t = $template $t = $t -creplace 'Name="JPEXS Free Flash Decompiler 1.0.0"', 'Name="JPEXS Free Flash Decompiler ${{ needs.compute-version.outputs.verLong }}"' $t = $t -creplace 'Version="1.0.0"', 'Version="${{ needs.compute-version.outputs.verRaw }}"' $t = $t -creplace 'Id="ARPVERSION" Value="1.0.0"', 'Id="ARPVERSION" Value="${{ needs.compute-version.outputs.verLong }}"' Set-Content -Path 'wix\Product.wxs' -Value $t -Encoding UTF8 - name: Create installer shell: cmd working-directory: wix run: | set MsiToolsPath=${{ steps.msitools.outputs.path }} .\Build_Release.cmd - name: Sign MSI with KMS key shell: pwsh run: | $signtool = "${{ steps.signtool.outputs.path }}" $kc = "projects/$env:GCP_PROJECT_ID/locations/$env:GCP_LOCATION/keyRings/$env:KMS_KEYRING/cryptoKeys/$env:KMS_KEY/cryptoKeyVersions/$env:KMS_KEY_VERSION" $ErrorActionPreference = 'Stop' $exe = $signtool $args = @( "sign", "/v", "/debug", "/fd", "sha256", "/tr", "http://timestamp.sectigo.com?td=sha256", "/td", "sha256", "/f", "$env:CERT_PATH", "/csp", "Google Cloud KMS Provider", "/kc", "$kc", "wix/bin/Release/FFDec.msi" ) # --- retry policy --- $maxAttempts = 5 $delaySeconds = 10 $needle = "SignTool Error: An unexpected internal error has occurred" for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { Write-Host "Attempt $attempt/${maxAttempts}: $exe $($args -join ' ')" $output = & $exe @args 2>&1 | Out-String $exitCode = $LASTEXITCODE if ($output) { Write-Host $output.TrimEnd() } if ($exitCode -eq 0) { Write-Host "Succeeded." exit 0 } $hasNeedle = $output -match [regex]::Escape($needle) if ($hasNeedle -and $attempt -lt $maxAttempts) { Write-Warning "Detected transient SignTool internal error. Retrying in $delaySeconds seconds..." Start-Sleep -Seconds $delaySeconds continue } if ($hasNeedle) { throw "Failed after $maxAttempts attempts due to repeated SignTool internal error (exit code $exitCode)." } else { throw "Command failed (exit code $exitCode). Output did not match retry condition." } } - name: Verify MSI signature shell: pwsh run: | $signtool = "${{ steps.signtool.outputs.path }}" & $signtool verify /pa /v "wix/bin/Release/FFDec.msi" - name: Upload signed EXE artifact uses: actions/upload-artifact@v4 with: name: signed_exe path: dist/ffdec.exe - name: Upload signed CLI EXE artifact uses: actions/upload-artifact@v4 with: name: signed_cli_exe path: dist/ffdec-cli.exe - name: Rename MSI shell: cmd run: | mkdir releases move wix\bin\Release\FFDec.msi releases\ffdec_${{ needs.compute-version.outputs.verShort }}.msi - name: Upload signed MSI artifact uses: actions/upload-artifact@v4 with: name: signed_msi path: releases/ffdec_${{ needs.compute-version.outputs.verShort }}.msi packages: name: Create packages runs-on: ubuntu-latest concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true needs: - compute-version - build - exe - sign_and_msi if: needs.compute-version.outputs.doRelease == 'true' steps: - name: Checkout uses: actions/checkout@v3 - name: Download dist artifact uses: actions/download-artifact@v4 with: name: dist path: dist/ - name: Download signed jar artifact uses: actions/download-artifact@v4 with: name: signed_jar path: dist/ - name: Download lib dist artifact uses: actions/download-artifact@v4 with: name: lib_dist path: libsrc/ffdec_lib/dist/ - name: Download signed lib jar artifact uses: actions/download-artifact@v4 with: name: signed_lib_jar path: libsrc/ffdec_lib/dist/ - name: Copy signed ffdec_lib.jar to dist/lib dir run: cp libsrc/ffdec_lib/dist/ffdec_lib.jar dist/lib/ffdec_lib.jar - name: Download signed EXE artifact uses: actions/download-artifact@v4 with: name: signed_exe path: dist/ - name: Download signed CLI EXE artifact uses: actions/download-artifact@v4 with: name: signed_cli_exe path: dist/ - name: Download signed MSI artifact uses: actions/download-artifact@v4 with: name: signed_msi path: releases/ - name: Set up JDK uses: actions/setup-java@v4 with: distribution: adopt architecture: x64 java-version: 8 - name: Set up Ant run: | sudo apt-get update -y -qq sudo apt-get install -y -qq ant - name: Download CHANGELOG.md artifact uses: actions/download-artifact@v4 with: name: changelog_md - name: Download version.properties artifact uses: actions/download-artifact@v4 with: name: version_properties - name: Copy version.properties to lib run: cp version.properties libsrc/ffdec_lib/ - name: Update Dockerfile if: needs.compute-version.outputs.verDebug == 'false' run: | sed -E -i 's|version[^/]+/ffdec_[^/]+\.zip|version${{needs.compute-version.outputs.verRaw}}/ffdec_${{needs.compute-version.outputs.verRaw}}.zip|g' Dockerfile - name: Upload Dockerfile if: needs.compute-version.outputs.verDebug == 'false' uses: actions/upload-artifact@v4 with: name: dockerfile path: Dockerfile - name: Update metainfo if: needs.compute-version.outputs.verDebug == 'false' run: | mkdir -p build/classes javac -cp build/classes -d build/classes src/com/jpexs/build/MetainfoUpdater.java java -cp build/classes com.jpexs.build.MetainfoUpdater - name: Upload metainfo if: needs.compute-version.outputs.verDebug == 'false' uses: actions/upload-artifact@v4 with: name: metainfo path: resources/com.jpexs.decompiler.flash.metainfo.xml - name: Create packages run: ant new-version-set release-no-dist - name: Upload packages uses: actions/upload-artifact@v4 with: name: packages path: releases deploy: name: Deploy to GitHub runs-on: ubuntu-latest if: needs.compute-version.outputs.doRelease == 'true' # This one is different from others concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false needs: - compute-version - packages steps: - name: Checkout uses: actions/checkout@v3 - name: Set up PHP run: | sudo apt-get update -y -qq sudo apt-get install -y -qq php - name: Download packages artifact uses: actions/download-artifact@v4 with: name: packages path: releases/ - name: Download lib javadoc artifact uses: actions/download-artifact@v4 with: name: lib_javadoc path: releases/ - name: Download CHANGELOG.md artifact uses: actions/download-artifact@v4 with: name: changelog_md - name: Generate nightly release description if: needs.compute-version.outputs.verDebug == 'true' run: php cicd_scripts/format_release_info.php -filever "${{ needs.compute-version.outputs.verShort }}" Unreleased ${{ needs.compute-version.outputs.verTag }} ./CHANGELOG.md "${{ env.CICD_REPO_SLUG }}" > ./release_notes.md - name: Generate release description if: needs.compute-version.outputs.verDebug == 'false' run: php cicd_scripts/format_release_info.php -filever "${{ needs.compute-version.outputs.verShort }}" "${{ needs.compute-version.outputs.verShort }}" ${{ needs.compute-version.outputs.verTag }} ./CHANGELOG.md "${{ env.CICD_REPO_SLUG }}" > ./release_notes.md - name: Download Dockerfile artifact if: needs.compute-version.outputs.verDebug == 'false' uses: actions/download-artifact@v4 with: name: dockerfile - name: Download metainfo artifact if: needs.compute-version.outputs.verDebug == 'false' uses: actions/download-artifact@v4 with: name: metainfo path: resources/ - name: Commit CHANGELOG, metainfo, Dockerfile if: needs.compute-version.outputs.verDebug == 'false' run: | git add CHANGELOG.md git add Dockerfile git add resources/com.jpexs.decompiler.flash.metainfo.xml git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git commit -m "chore: change version to ${{ needs.compute-version.outputs.verRaw }}" -m "[ci skip]" git push origin master - name: Upload release notes artifact uses: actions/upload-artifact@v4 with: name: release_notes path: ./release_notes.md - name: Create tag run: | TAG="${{ needs.compute-version.outputs.verTag }}" git fetch --tags --force if git rev-parse "$TAG" >/dev/null 2>&1; then echo "Tag $TAG already exists -> skipping tag creation." exit 0 fi git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git tag "$TAG" git push origin "$TAG" - name: Create GitHub Release + upload assets uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.compute-version.outputs.verTag }} name: ${{ needs.compute-version.outputs.verTitle }} body_path: ./release_notes.md preserve_order: true prerelease: ${{ needs.compute-version.outputs.verDebug == 'true'}} fail_on_unmatched_files: true files: | releases/ffdec_${{ needs.compute-version.outputs.verShort }}.msi releases/ffdec_${{ needs.compute-version.outputs.verShort }}.zip releases/ffdec_${{ needs.compute-version.outputs.verShort }}.deb releases/ffdec_${{ needs.compute-version.outputs.verShort }}.pkg releases/ffdec_${{ needs.compute-version.outputs.verShort }}_macosx.zip releases/ffdec_lib_${{ needs.compute-version.outputs.verShort }}.zip releases/ffdec_lib_javadoc_${{ needs.compute-version.outputs.verShort }}.zip - name: Remove old nightly if: needs.compute-version.outputs.verDebug == 'true' env: GH_TOKEN: ${{ github.token }} run: | gh release delete ${{needs.compute-version.outputs.verOldTag}} --yes --cleanup-tag - name: Upload to Nexus Mods if: needs.compute-version.outputs.verDebug == 'false' uses: Nexus-Mods/upload-action@v1.0.0-beta.5 with: api_key: ${{ secrets.NEXUSMODS_API_KEY }} file_group_id: 7255064 filename: "releases/ffdec_${{ needs.compute-version.outputs.verShort }}.zip" version: ${{ needs.compute-version.outputs.verShort }} display_name: "FFDec - Zipped" file_category: main allow_mod_manager_download: false