From 6e5c6b83f05e71428a94125b8cf8bb103e69b631 Mon Sep 17 00:00:00 2001 From: Bruno Lopes Date: Sat, 28 Mar 2026 11:17:13 -0300 Subject: [PATCH] Revert "build/windows: No need to download .NET for PowerShell" This reverts commit 963c896313bf527e57c110fa42b32fb3247394e0. Ouch, here we go again with git flaky LF/CRLF line ending handling. --- build/windows/store/3_dist-gimp-winsdk.ps1 | 1052 ++++++++++---------- 1 file changed, 526 insertions(+), 526 deletions(-) diff --git a/build/windows/store/3_dist-gimp-winsdk.ps1 b/build/windows/store/3_dist-gimp-winsdk.ps1 index ab9cde0041..d966f88d39 100644 --- a/build/windows/store/3_dist-gimp-winsdk.ps1 +++ b/build/windows/store/3_dist-gimp-winsdk.ps1 @@ -1,526 +1,526 @@ -#!/usr/bin/env pwsh - -# Parameters -param ($revision = "$GIMP_CI_MS_STORE", - $wack = 'Non-WACK', - $build_dir, - $arm64_bundle = 'gimp-clangarm64', - $x64_bundle = 'gimp-clang64') - -# Ensure the script work properly -$ErrorActionPreference = 'Stop' -$PSNativeCommandUseErrorActionPreference = $false #to ensure error catching as in pre-7.4 PS -if (-not (Test-Path build\windows\store) -and -not (Test-Path 3_dist-gimp-winsdk.ps1 -Type Leaf) -or $PSScriptRoot -notlike "*build\windows\store*") - { - Write-Host '(ERROR): Script called from wrong dir. Please, call the script from gimp source.' -ForegroundColor Red - exit 1 - } -elseif (Test-Path 3_dist-gimp-winsdk.ps1 -Type Leaf) - { - Set-Location ..\..\.. - } -if (-not $GITLAB_CI) - { - $PARENT_DIR = '..\' - } - - -# 1. AUTODECTET LATEST WINDOWS SDK AND MSSTORE-CLI -Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_tlkt$([char]13)$([char]27)[0KChecking WinSDK and other tools installation" -if ((Get-WmiObject -Class Win32_ComputerSystem).SystemType -like 'ARM64*') - { - $cpu_arch = 'arm64' - } -else - { - $cpu_arch = 'x64' - } - -## Windows SDK -$win_sdk_version = Get-ItemProperty Registry::'HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty ProductVersion -if ("$win_sdk_version" -eq '') - { - $xmlObject = New-Object XML - $xmlObject.Load("$PWD\build\windows\store\AppxManifest.xml") - $nt_build_max = (($xmlObject.Package.Dependencies.TargetDeviceFamily.MaxVersionTested | Out-String).Trim()) -replace '..$','' - Write-Host "(ERROR): Windows SDK installation not found. Please, install it with: winget install Microsoft.WindowsSDK.${nt_build_max}" -ForegroundColor Red - exit 1 - } -$win_sdk_path = Get-ItemProperty Registry::'HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' | Select-Object -ExpandProperty InstallationFolder -$env:PATH = "${win_sdk_path}bin\${win_sdk_version}.0\$cpu_arch;${win_sdk_path}App Certification Kit;" + $env:PATH - -## msstore-cli (ONLY FOR RELEASES) -if ("$CI_COMMIT_TAG" -eq (git describe --all | Foreach-Object {$_ -replace 'tags/',''})) - { - #.NET runtime required by msstore-cli - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - $msstore_tag = (Invoke-RestMethod 'https://api.github.com/repos/microsoft/msstore-cli/releases/latest').tag_name - $dotnet_msstore = ((Invoke-RestMethod "https://raw.githubusercontent.com/microsoft/msstore-cli/refs/heads/rel/$msstore_tag/MSStore.API/MSStore.API.csproj").Project.PropertyGroup.TargetFramework | Out-String).Trim() - $powershell_tag = (Invoke-RestMethod 'https://api.github.com/repos/PowerShell/PowerShell/releases/latest').tag_name - #$dotnet_powershell = ((Invoke-RestMethod "https://raw.githubusercontent.com/PowerShell/PowerShell/refs/tags/$powershell_tag/PowerShell.Common.props").Project.PropertyGroup.TargetFramework | Out-String).Trim() - foreach ($dotnet in $dotnet_msstore) - { - $dotnet_major = ($dotnet | Out-String) -replace "`r`n",'' -replace 'net','' - $dotnet_tag = ((Invoke-RestMethod "https://api.github.com/repos/dotnet/runtime/releases").tag_name | Select-String "$dotnet_major" | Select-Object -First 1).ToString() -replace 'v','' - if (-not (Test-Path "$Env:ProgramFiles\dotnet\shared\Microsoft.NETCore.App\$dotnet_major*\")) - { - Write-Output "(INFO): downloading .NET v$dotnet_tag" - Invoke-WebRequest "https://aka.ms/dotnet/$dotnet_major/dotnet-runtime-win-$cpu_arch.zip" -UseBasicParsing -OutFile ${PARENT_DIR}dotnet-runtime-${dotnet_major}.zip - Expand-Archive ${PARENT_DIR}dotnet-runtime-${dotnet_major}.zip ${PARENT_DIR}dotnet-runtime -Force - $env:PATH = "$(Resolve-Path $PWD\${PARENT_DIR}dotnet-runtime);" + $env:PATH - $env:DOTNET_ROOT = "$(Resolve-Path $PWD\${PARENT_DIR}dotnet-runtime)" - } - } - - #powershell (with bundled .NET runtime) required by msstore-cli. See: https://github.com/microsoft/msstore-cli/issues/70 - if (-not (Test-Path Registry::'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths\pwsh.exe') -and $PSVersionTable.PSVersion.Major -lt 6) - { - Write-Output "(INFO): downloading PowerShell $powershell_tag" - Invoke-WebRequest "https://github.com/PowerShell/PowerShell/releases/download/$powershell_tag/PowerShell-$($powershell_tag -replace 'v','')-win-$cpu_arch.zip" -UseBasicParsing -OutFile ${PARENT_DIR}PowerShell.zip - Expand-Archive ${PARENT_DIR}PowerShell.zip ${PARENT_DIR}PowerShell -Force - $env:PATH = "$(Resolve-Path $PWD\${PARENT_DIR}PowerShell);" + $env:PATH - } - - #msstore-cli itself - if (-not (Test-Path Registry::'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths\MSStore.exe')) - { - Write-Output "(INFO): downloading MSStoreCLI $msstore_tag" - Invoke-WebRequest "https://github.com/microsoft/msstore-cli/releases/download/$msstore_tag/MSStoreCLI-win-$cpu_arch.zip" -UseBasicParsing -OutFile ${PARENT_DIR}MSStoreCLI.zip - Expand-Archive ${PARENT_DIR}MSStoreCLI.zip ${PARENT_DIR}MSStoreCLI -Force - $env:PATH = "$(Resolve-Path $PWD\${PARENT_DIR}MSStoreCLI);" + $env:PATH - } - $msstore_text = " | Installed MSStoreCLI: $(msstore --version)" - } -Write-Output "(INFO): Installed WinSDK: ${win_sdk_version}${msstore_text}" -Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_tlkt$([char]13)$([char]27)[0K" - - -# 2. GLOBAL VARIABLES -Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_info$([char]13)$([char]27)[0KGetting MSIX global info" -if (-not $build_dir) - { - $build_dir = Get-ChildItem _build* | Select-Object -First 1 - } -$config_path = "$build_dir\config.h" -if (-not (Test-Path "$config_path")) - { - Write-Host "(ERROR): config.h file not found. You can run 'build/windows/2_build-gimp-msys2.ps1' or configure GIMP to generate it." -ForegroundColor red - exit 1 - } -ForEach ($line in $(Select-String 'define' $config_path -AllMatches)) - { - Invoke-Expression $($line -replace '^.*#' -replace 'define ','$' -replace ' ','=') - } - -## Get Identity Name (the dir shown in Explorer) -if (-not $GIMP_RELEASE -or $GIMP_IS_RC_GIT) - { - $IDENTITY_NAME="GIMP.GIMPInsider" - } -elseif ($GIMP_RELEASE -and $GIMP_UNSTABLE -or $GIMP_RC_VERSION) - { - $IDENTITY_NAME="GIMP.GIMPPreview" - } -else - { - $IDENTITY_NAME="GIMP.43237F745459" - } - -## Get custom GIMP version (major.minor.micro+revision.0), a compliant way to publish to Partner Center: -## https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/app-package-requirements) -### Get GIMP app version (major.minor) -$major = ($GIMP_APP_VERSION.Split('.'))[0] -$minor = ($GIMP_APP_VERSION.Split('.'))[-1] -### Get GIMP micro version -$micro = ($GIMP_VERSION.Split('.'))[-1] -replace '(.+?)-.+','$1' -if ($micro -ne '0') - { - $micro_digit = $micro - } -### Get GIMP revision -if ($revision -match '[1-9]' -and $CI_PIPELINE_SOURCE -ne 'schedule') - { - $revision_text = ", revision: $revision" - } -elseif ($GIMP_RC_VERSION) - { - $revision = $GIMP_RC_VERSION - $revision_text = ", RC: $revision" - } -else - { - $wack = "$revision" - $revision = "0" - } -$CUSTOM_GIMP_VERSION = "$GIMP_APP_VERSION.${micro_digit}${revision}.0" -Write-Output "(INFO): Identity: $IDENTITY_NAME | Version: $CUSTOM_GIMP_VERSION (major: $major, minor: $minor, micro: ${micro}${revision_text})" - -## Autodetects what arch bundles will be packaged -if (-not (Test-Path "$arm64_bundle") -and -not (Test-Path "$x64_bundle")) - { - Write-Host "(ERROR): No bundle found. You can tweak 'build/windows/2_build-gimp-msys2.ps1' or configure GIMP with '-Dms-store=true' to make one." -ForegroundColor red - exit 1 - } -elseif ((Test-Path "$arm64_bundle") -and -not (Test-Path "$x64_bundle")) - { - Write-Output "(INFO): Arch: arm64" - $supported_archs = "$arm64_bundle" - } -elseif (-not (Test-Path "$arm64_bundle") -and (Test-Path "$x64_bundle")) - { - Write-Output "(INFO): Arch: x64" - $supported_archs = "$x64_bundle" - } -elseif ((Test-Path "$arm64_bundle") -and (Test-Path "$x64_bundle")) - { - Write-Output "(INFO): Arch: arm64 and x64" - $supported_archs = "$arm64_bundle","$x64_bundle" - $temp_text='temporary ' - } -Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_info$([char]13)$([char]27)[0K" - - -foreach ($bundle in $supported_archs) - { - if (("$bundle" -like '*a64*') -or ("$bundle" -like '*aarch64*') -or ("$bundle" -like '*arm64*')) - { - $msix_arch = 'arm64' - } - else - { - $msix_arch = 'x64' - } - ## Create temporary dir - if (Test-Path $msix_arch) - { - Remove-Item $msix_arch/ -Recurse - } - New-Item $msix_arch -ItemType Directory | Out-Null - ### Prevent Git going crazy - $ig_content = "`n$msix_arch" - if (Test-Path .gitignore -Type Leaf) - { - if (-not (Test-Path .gitignore.bak -Type Leaf)) - { - Copy-Item .gitignore .gitignore.bak - } - Add-Content .gitignore "$ig_content" - } - else - { - New-Item .gitignore | Out-Null - Set-Content .gitignore "$ig_content" - } - - - # 3. PREPARE MSIX "SOURCE" - Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_source[collapsed=true]$([char]13)$([char]27)[0KMaking assets for $msix_arch MSIX" - # (We test the existence of the icons here (and not on section 3.2.) to avoid creating AppxManifest.xml for nothing) - $icons_path = "$build_dir\build\windows\store\Assets" - if (-not (Test-Path "$icons_path")) - { - Write-Host "(ERROR): MS Store icons not found. You can tweak 'build/windows/2_build-gimp-msys2.ps1' or configure GIMP with '-Dms-store=true' to build them." -ForegroundColor red - exit 1 - } - - ## 3.1. CONFIGURE MANIFEST - Write-Output "(INFO): configuring AppxManifest.xml for $msix_arch" - Copy-Item build\windows\store\AppxManifest.xml $msix_arch - function conf_manifest ([string]$search, [string]$replace) - { - (Get-Content $msix_arch\AppxManifest.xml) | Foreach-Object {$_ -replace "$search","$replace"} | - Set-Content $msix_arch\AppxManifest.xml - } - ### Set msix_arch - conf_manifest 'neutral' "$msix_arch" - ### Set Identity Name - conf_manifest '@IDENTITY_NAME@' "$IDENTITY_NAME" - ### Set Display Name (the name shown in MS Store, on Start Menu etc) - if (-not $GIMP_RELEASE -or $GIMP_IS_RC_GIT) - { - $display_name='GIMP (Insider)' - } - elseif (($GIMP_RELEASE -and $GIMP_UNSTABLE) -or $GIMP_RC_VERSION) - { - $display_name='GIMP (Preview)' - } - else - { - $display_name='GIMP' - } - conf_manifest '@DISPLAY_NAME@' "$display_name" - ### Set custom GIMP version (major.minor.micro+revision.0) - conf_manifest '@CUSTOM_GIMP_VERSION@' "$CUSTOM_GIMP_VERSION" - #### Needed to differentiate on PowerShell etc - if ($GIMP_RELEASE -and -not $GIMP_IS_RC_GIT) - { - $mutex_suffix="-$GIMP_MUTEX_VERSION" - } - conf_manifest '@MUTEX_SUFFIX@' "$mutex_suffix" - ### List supported filetypes - $file_types = Get-Content "$build_dir\plug-ins\file_associations.list" | Foreach-Object {" ." + $_} | - Foreach-Object {$_ + ""} | Where-Object {$_ -notmatch 'xcf'} - (Get-Content $msix_arch\AppxManifest.xml) | Foreach-Object {$_ -replace "@FILE_TYPES@","$file_types"} | - Set-Content $msix_arch\AppxManifest.xml - - ## 3.2. CREATE ICON ASSETS - Write-Output "(INFO): generating resources*.pri from $icons_path" - ### Copy pre-generated icons to msix_arch\Assets - New-Item $msix_arch\Assets -ItemType Directory | Out-Null - Copy-Item "$icons_path\*.png" $msix_arch\Assets\ -Recurse - ### Generate temp priconfig.xml then resources*.pri - Set-Location $msix_arch - makepri createconfig /cf priconfig.xml /dq lang-en-US /pv 10.0.0 | Out-File ..\winsdk.log; if ("$LASTEXITCODE" -gt '0') { exit 1 } - Set-Location ..\ - makepri new /pr $msix_arch /cf $msix_arch\priconfig.xml /of $msix_arch | Out-File winsdk.log -Append; if ("$LASTEXITCODE" -gt '0') { exit 1 } - Remove-Item $msix_arch\priconfig.xml - Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_source$([char]13)$([char]27)[0K" - - - # 4. COPY GIMP FILES - Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_files[collapsed=true]$([char]13)$([char]27)[0KPreparing GIMP files in $msix_arch VFS" - $vfs = "$msix_arch\VFS\ProgramFilesX64\GIMP" - ## Copy files into VFS folder (to support external 3P plug-ins) - Copy-Item "$bundle" "$vfs" -Recurse -Force - - ## MSIX-specific adjustments (needed because we use the same gimp-* bundle as base to both .exe and .msix) - ### Set revision on about dialog (this does the same as '-Drevision' build option) - if (-not $GIMP_RC_VERSION) - { - (Get-Content "$vfs\share\gimp\*\gimp-release") | Foreach-Object {$_ -replace "revision=0","revision=$revision"} | - Set-Content "$vfs\share\gimp\*\gimp-release" - } - ### Disable Update check (ONLY FOR RELEASES) - if ($GIMP_RELEASE -and -not $GIMP_IS_RC_GIT) - { - Add-Content "$vfs\share\gimp\*\gimp-release" 'check-update=false' - } - - ## Parity adjustments (to make the .msix IDENTICAL TO THE .EXE INSTALLER, except for the adjustments above) - Get-ChildItem "$vfs" -Recurse -Include (".gitignore", "gimp.cmd") | Remove-Item -Recurse - Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_files$([char]13)$([char]27)[0K" - - - # 5.A. MAKE .MSIX AND CORRESPONDING .APPXSYM - Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_making[collapsed=true]$([char]13)$([char]27)[0KPackaging ${temp_text}$msix_arch MSIX" - ## Make .appxsym for each msix_arch (ONLY FOR RELEASES) - $APPXSYM = "${IDENTITY_NAME}_${CUSTOM_GIMP_VERSION}_$msix_arch.appxsym" - if ($CI_COMMIT_TAG -match 'GIMP_[0-9]*_[0-9]*_[0-9]*' -or $GIMP_CI_MS_STORE -like 'MSIXUPLOAD*') - { - Write-Output "(INFO): making $APPXSYM" - Get-ChildItem $msix_arch -Filter *.pdb -Recurse | Compress-Archive -DestinationPath "$APPXSYM.zip" - Rename-Item "$APPXSYM.zip" "$APPXSYM" - #(To not ship .pdb we need an online symbol server pointed on _NT_SYMBOL_PATH) - #Get-ChildItem $msix_arch -Include *.pdb -Recurse -Force | Remove-Item -Recurse -Force - } - - ## Make .msix from each msix_arch - $MSIX_ARTIFACT = $APPXSYM -replace '.appxsym','.msix' - Write-Output "(INFO): packaging $MSIX_ARTIFACT" - makeappx pack /d $msix_arch /p $MSIX_ARTIFACT /o | Out-File winsdk.log -Append; if ("$LASTEXITCODE" -gt '0') { exit 1 } - Remove-Item $msix_arch/ -Recurse - Remove-Item .gitignore - if (Test-Path .gitignore.bak -Type Leaf) - { - Rename-Item .gitignore.bak .gitignore - } - Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_making$([char]13)$([char]27)[0K" - } #END of 'foreach ($bundle' - - -# 5.B. MAKE .MSIXBUNDLE OR SUBSEQUENT .MSIXUPLOAD -if (((Test-Path $arm64_bundle) -and (Test-Path $x64_bundle)) -and (Get-ChildItem *.msix -Recurse).Count -gt 1) - { - $MSIXBUNDLE = "${IDENTITY_NAME}_${CUSTOM_GIMP_VERSION}_neutral.msixbundle" - $MSIX_ARTIFACT = "$MSIXBUNDLE" - if ($GIMP_RELEASE -and -not $GIMP_IS_RC_GIT) - { - $MSIXUPLOAD = "${IDENTITY_NAME}_${CUSTOM_GIMP_VERSION}_x64_arm64_bundle.msixupload" - $MSIX_ARTIFACT = "$MSIXUPLOAD" - } - Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_making[collapsed=true]$([char]13)$([char]27)[0KPackaging $MSIX_ARTIFACT" - - ## Make .msixbundle with all archs - ## (This is needed not only for easier multi-arch testing but - ## also to make sure against Partner Center getting confused) - New-Item _TempOutput -ItemType Directory | Out-Null - Move-Item *.msix _TempOutput/ - makeappx bundle /bv "${CUSTOM_GIMP_VERSION}" /d _TempOutput /p $MSIXBUNDLE /o; if ("$LASTEXITCODE" -gt '0') { exit 1 } - Remove-Item _TempOutput/ -Recurse - - ## Make .msixupload (ONLY FOR RELEASES) - if ($GIMP_RELEASE -and -not $GIMP_IS_RC_GIT) - { - Write-Output "(INFO): creating $MSIXUPLOAD for submission" - Compress-Archive -Path "*.appxsym","*.msixbundle" -DestinationPath "$MSIXUPLOAD.zip" - Rename-Item "$MSIXUPLOAD.zip" "$MSIXUPLOAD" - Remove-Item *.appxsym -Force - Remove-Item *.msixbundle -Force - } - Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_making$([char]13)$([char]27)[0K" - } - - -# 6.A. CERTIFY .MSIX OR .MSIXBUNDLE WITH WACK (OPTIONAL) -# (Partner Center does the same thing before publishing) -if (-not $GITLAB_CI -and $wack -eq 'WACK') - { - Write-Output "(INFO): certifying $MSIX_ARTIFACT with WACK" - ## Prepare file naming - ## (appcert CLI does NOT allow relative paths) - $fullpath = $PWD - ## (appcert CLI does NOT allow more than one dot on xml name) - $xml_artifact = "$MSIX_ARTIFACT" -replace '.msix', '-report.xml' -replace 'bundle', '' - - ## Generate detailed report - ## (appcert only works with admin rights so let's use sudo) - $nt_build = [System.Environment]::OSVersion.Version | Select-Object -ExpandProperty Build - if ($nt_build -lt '26052') - { - Write-Host "(ERROR): Certification from CLI requires 'sudo' (available only for build 10.0.26052.0 and above)" -ForegroundColor Red - exit 1 - } - $sudo_mode = Get-ItemProperty Registry::'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Sudo' | Select-Object -ExpandProperty Enabled - if ($sudo_mode -eq '0') - { - Write-Host "(ERROR): 'sudo' is disabled. Please enable it in Settings." -ForegroundColor Red - exit 1 - } - elseif ($sudo_mode -ne '3') - { - Write-Host "(ERROR): 'sudo' is not in normal/inline mode. Please change it in Settings." -ForegroundColor Red - exit 1 - } - sudo appcert test -appxpackagepath $fullpath\$MSIX_ARTIFACT -reportoutputpath $fullpath\$xml_artifact - - ## Output overall result - if (Test-Path $xml_artifact -Type Leaf) - { - $xmlObject = New-Object XML - $xmlObject.Load("$xml_artifact") - $result = $xmlObject.REPORT.OVERALL_RESULT - if ($result -eq 'FAIL') - { - Write-Host "(ERROR): $MSIX_ARTIFACT not passed. See: $xml_artifact" -ForegroundColor Red - exit 1 - } - elseif ($result -eq 'WARNING') - { - Write-Host "(WARNING): $MSIX_ARTIFACT passed partially. See: $xml_artifact" -ForegroundColor Yellow - } - #elseif ($result -eq 'PASS') - #{ - # Output nothing - #} - } - } - - -# 6.B. SIGN .MSIX OR .MSIXBUNDLE PACKAGE (NOT THE BINARIES) -# (Partner Center does the same thing, for free, before publishing) -if (-not $GIMP_RELEASE -or $GIMP_IS_RC_GIT) - { - Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_trust${msix_arch}[collapsed=true]$([char]13)$([char]27)[0KSelf-signing $MSIX_ARTIFACT (for testing purposes)" - ## Check if certificate for nightly builds is fine - $sign_output = & signtool sign /debug /fd sha256 /a /f $(Resolve-Path build\windows\store\pseudo-gimp*.pfx) /p eek $MSIX_ARTIFACT - Write-Output $sign_output - if ($sign_output -like "*After expiry filter, 0 certs were left*") - { - $pseudo_gimp = "pseudo-gimp_$(Get-Date -UFormat %s -Millisecond 0)" - New-SelfSignedCertificate -Type Custom -Subject "$(([xml](Get-Content build\windows\store\AppxManifest.xml)).Package.Identity.Publisher)" -KeyUsage DigitalSignature -FriendlyName "$pseudo_gimp" -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") | Out-Null - Export-PfxCertificate -Cert "Cert:\CurrentUser\My\$(Get-ChildItem Cert:\CurrentUser\My | Where-Object FriendlyName -EQ "$pseudo_gimp" | Select-Object -ExpandProperty Thumbprint)" -FilePath "${pseudo_gimp}.pfx" -Password (ConvertTo-SecureString -String eek -Force -AsPlainText) | Out-Null - Remove-Item "Cert:\CurrentUser\My\$(Get-ChildItem Cert:\CurrentUser\My | Where-Object FriendlyName -EQ "$pseudo_gimp" | Select-Object -ExpandProperty Thumbprint)" - Write-Host "(ERROR): Self-signing certificate expired. Please commit the generated '${pseudo_gimp}.pfx' on 'build\windows\store\'." -ForegroundColor red - $sha256_pfx = (Get-FileHash "${pseudo_gimp}.pfx" -Algorithm SHA256 | Select-Object -ExpandProperty Hash).ToLower() - Write-Output "(INFO): ${pseudo_gimp}.pfx SHA-256: $sha256_pfx" - } - else - { - Copy-Item build\windows\store\pseudo-gimp*.pfx pseudo-gimp.pfx -Recurse - - ## Generate checksums - $sha256 = (Get-FileHash $MSIX_ARTIFACT -Algorithm SHA256 | Select-Object -ExpandProperty Hash).ToLower() - Write-Output "(INFO): $MSIX_ARTIFACT SHA-256: $sha256" - $sha512 = (Get-FileHash $MSIX_ARTIFACT -Algorithm SHA512 | Select-Object -ExpandProperty Hash).ToLower() - Write-Output "(INFO): $MSIX_ARTIFACT SHA-512: $sha512" - } - - ## Check in advance if the CLIENT_SECRET we will use in the future tagged pipeline is fine - if ($CI_COMMIT_TAG) - { - $latest_msix_secret = New-Object -TypeName System.DateTime -ArgumentList 2026, 9, 20 - Write-Output "(INFO): CLIENT_SECRET expire date is: $latest_msix_secret" - if ((Get-Date) -ge $latest_msix_secret) - { - $expired_secret = "$latest_msix_secret" - Write-Host "(ERROR): Submission secret for releases expired. Please follow https://developer.gimp.org/core/maintainer/accounts/msstore/ then commit its expire date on 'build\windows\store\*.ps1'." -ForegroundColor red - } - } - Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_trust${msix_arch}$([char]13)$([char]27)[0K" - } - - -if ($GITLAB_CI) - { - # GitLab doesn't support wildcards when using "expose_as" so let's move to a dir - $OUTPUT_DIR = "$PWD\build\windows\store\_Output" - New-Item $OUTPUT_DIR -ItemType Directory -Force | Out-Null - if (-not $GIMP_RELEASE -or $GIMP_IS_RC_GIT) - { - Move-Item pseudo-gimp*.pfx $OUTPUT_DIR - } - if ($sha256_pfx -or $expired_secret) - { - exit 1 - } - Move-Item $MSIX_ARTIFACT $OUTPUT_DIR -Force - } - - -# 7. SUBMIT .MSIXUPLOAD TO MS STORE (ONLY FOR RELEASES) -if ("$CI_COMMIT_TAG" -eq (git describe --all | Foreach-Object {$_ -replace 'tags/',''})) - { - Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_submission[collapsed=true]$([char]13)$([char]27)[0KSubmitting $MSIX_ARTIFACT to Microsoft Store" - ## Needed credentials for submission - ### Connect with our Microsoft Entra credentials (stored on GitLab) - ### (The last one can be revoked at any time in MS Entra Admin center) - msstore reconfigure --tenantId $TENANT_ID --sellerId $SELLER_ID --clientId $CLIENT_ID --clientSecret $CLIENT_SECRET; if ("$LASTEXITCODE" -gt '0') { exit 1 } - ### Set product_id (which is not confidential) needed by HTTP calls of some commands - if ($GIMP_UNSTABLE -or $GIMP_RC_VERSION) - { - $env:PRODUCT_ID="9NZVDVP54JMR" - } - else - { - $env:PRODUCT_ID="9PNSJCLXDZ0V" - } - - ## Create submission and upload .msixupload file to it - msstore publish $OUTPUT_DIR\$MSIX_ARTIFACT -id $env:PRODUCT_ID -nc; if ("$LASTEXITCODE" -gt '0') { exit 1 } - - ## Update submission info (if PS6 or up. Check the section 1 of this script) - $env:GIMP_VERSION="$GIMP_VERSION" - pwsh -Command ` - { - $jsonObject = msstore submission get $env:PRODUCT_ID | ConvertFrom-Json -AsHashtable; if ("$LASTEXITCODE" -gt '0') { exit 1 } - ###Get changelog from Linux appdata - $xmlObject = New-Object XML - $xmlObject.Load("$PWD\desktop\org.gimp.GIMP.appdata.xml.in.in") - if ($xmlObject.component.releases.release[0].version -ne ("$env:GIMP_VERSION".ToLower() -replace '-','~')) - { - #This check is needed to ensure the right release notes etc when the submission is (rarely) done manually/locally - Write-Host "(WARNING): appdata does not match main meson file. Submission info can't be updated." -ForegroundColor yellow - exit 1 - } - $jsonObject."Listings"."en-us"."BaseListing".'ShortDescription' = ($xmlObject.component.summary).Trim() - $jsonObject."Listings"."en-us"."BaseListing".'Description' = ($xmlObject.component.description.SelectNodes(".//p") | ForEach-Object { ($_.InnerText).Trim() -replace '\s*\r?\n\s*', ' ' } ) -join "`n`n" - #NOTE: Submission API does not allow more than 1500 chars on ReleaseNotes so we skip some

or

  • when needed - $jsonObject."Listings"."en-us"."BaseListing".'ReleaseNotes' = ($xmlObject.component.releases.release[0].description.SelectNodes(".//p | .//li") | ForEach-Object -Begin {$len=0} -Process { $text = ($_.InnerText).Trim() -replace '\s*\r?\n\s*', ' '; $formatted = if ($_.Name -eq 'li') { "- $text" } else { $text }; if (($len + $formatted.Length + 1) -lt 1490) { $len += $formatted.Length + 1; $formatted } }) -join "`n" - ###Send submission info - msstore submission updateMetadata $env:PRODUCT_ID ($jsonObject | ConvertTo-Json -Depth 100); if ("$LASTEXITCODE" -gt '0') { exit 1 } - } - - ## Start certification then publishing - msstore submission publish $env:PRODUCT_ID; if ("$LASTEXITCODE" -gt '0') { exit 1 } - Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_submission$([char]13)$([char]27)[0K" - } +#!/usr/bin/env pwsh + +# Parameters +param ($revision = "$GIMP_CI_MS_STORE", + $wack = 'Non-WACK', + $build_dir, + $arm64_bundle = 'gimp-clangarm64', + $x64_bundle = 'gimp-clang64') + +# Ensure the script work properly +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $false #to ensure error catching as in pre-7.4 PS +if (-not (Test-Path build\windows\store) -and -not (Test-Path 3_dist-gimp-winsdk.ps1 -Type Leaf) -or $PSScriptRoot -notlike "*build\windows\store*") + { + Write-Host '(ERROR): Script called from wrong dir. Please, call the script from gimp source.' -ForegroundColor Red + exit 1 + } +elseif (Test-Path 3_dist-gimp-winsdk.ps1 -Type Leaf) + { + Set-Location ..\..\.. + } +if (-not $GITLAB_CI) + { + $PARENT_DIR = '..\' + } + + +# 1. AUTODECTET LATEST WINDOWS SDK AND MSSTORE-CLI +Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_tlkt$([char]13)$([char]27)[0KChecking WinSDK and other tools installation" +if ((Get-WmiObject -Class Win32_ComputerSystem).SystemType -like 'ARM64*') + { + $cpu_arch = 'arm64' + } +else + { + $cpu_arch = 'x64' + } + +## Windows SDK +$win_sdk_version = Get-ItemProperty Registry::'HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty ProductVersion +if ("$win_sdk_version" -eq '') + { + $xmlObject = New-Object XML + $xmlObject.Load("$PWD\build\windows\store\AppxManifest.xml") + $nt_build_max = (($xmlObject.Package.Dependencies.TargetDeviceFamily.MaxVersionTested | Out-String).Trim()) -replace '..$','' + Write-Host "(ERROR): Windows SDK installation not found. Please, install it with: winget install Microsoft.WindowsSDK.${nt_build_max}" -ForegroundColor Red + exit 1 + } +$win_sdk_path = Get-ItemProperty Registry::'HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' | Select-Object -ExpandProperty InstallationFolder +$env:PATH = "${win_sdk_path}bin\${win_sdk_version}.0\$cpu_arch;${win_sdk_path}App Certification Kit;" + $env:PATH + +## msstore-cli (ONLY FOR RELEASES) +if ("$CI_COMMIT_TAG" -eq (git describe --all | Foreach-Object {$_ -replace 'tags/',''})) + { + #.NET runtime required by msstore-cli (and its PowerShell counterpart) + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $msstore_tag = (Invoke-RestMethod 'https://api.github.com/repos/microsoft/msstore-cli/releases/latest').tag_name + $dotnet_msstore = (Invoke-RestMethod "https://raw.githubusercontent.com/microsoft/msstore-cli/refs/heads/rel/$msstore_tag/MSStore.API/MSStore.API.csproj").Project.PropertyGroup.TargetFramework + $powershell_tag = (Invoke-RestMethod 'https://api.github.com/repos/PowerShell/PowerShell/releases/latest').tag_name + $dotnet_powershell = (Invoke-RestMethod "https://raw.githubusercontent.com/PowerShell/PowerShell/refs/tags/$powershell_tag/PowerShell.Common.props").Project.PropertyGroup.TargetFramework + foreach ($dotnet in $dotnet_msstore, $dotnet_powershell) + { + $dotnet_major = ($dotnet | Out-String) -replace "`r`n",'' -replace 'net','' + $dotnet_tag = ((Invoke-RestMethod "https://api.github.com/repos/dotnet/runtime/releases").tag_name | Select-String "$dotnet_major" | Select-Object -First 1).ToString() -replace 'v','' + if (-not (Test-Path "$Env:ProgramFiles\dotnet\shared\Microsoft.NETCore.App\$dotnet_major*\") -and -not (Test-Path "${PARENT_DIR}dotnet-runtime-${dotnet_major}")) + { + Write-Output "(INFO): downloading .NET v$dotnet_tag" + Invoke-WebRequest "https://aka.ms/dotnet/$dotnet_major/dotnet-runtime-win-$cpu_arch.zip" -UseBasicParsing -OutFile ${PARENT_DIR}dotnet-runtime-${dotnet_major}.zip + Expand-Archive ${PARENT_DIR}dotnet-runtime-${dotnet_major}.zip ${PARENT_DIR}dotnet-runtime -Force + $env:PATH = "$(Resolve-Path $PWD\${PARENT_DIR}dotnet-runtime);" + $env:PATH + $env:DOTNET_ROOT = "$(Resolve-Path $PWD\${PARENT_DIR}dotnet-runtime)" + } + } + + #powershell required by msstore-cli. See: https://github.com/microsoft/msstore-cli/issues/70 + if (-not (Test-Path Registry::'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths\pwsh.exe') -and $PSVersionTable.PSVersion.Major -lt 6) + { + Write-Output "(INFO): downloading PowerShell $powershell_tag" + Invoke-WebRequest "https://github.com/PowerShell/PowerShell/releases/download/$powershell_tag/PowerShell-$($powershell_tag -replace 'v','')-win-$cpu_arch.zip" -UseBasicParsing -OutFile ${PARENT_DIR}PowerShell.zip + Expand-Archive ${PARENT_DIR}PowerShell.zip ${PARENT_DIR}PowerShell -Force + $env:PATH = "$(Resolve-Path $PWD\${PARENT_DIR}PowerShell);" + $env:PATH + } + + #msstore-cli itself + if (-not (Test-Path Registry::'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths\MSStore.exe')) + { + Write-Output "(INFO): downloading MSStoreCLI $msstore_tag" + Invoke-WebRequest "https://github.com/microsoft/msstore-cli/releases/download/$msstore_tag/MSStoreCLI-win-$cpu_arch.zip" -UseBasicParsing -OutFile ${PARENT_DIR}MSStoreCLI.zip + Expand-Archive ${PARENT_DIR}MSStoreCLI.zip ${PARENT_DIR}MSStoreCLI -Force + $env:PATH = "$(Resolve-Path $PWD\${PARENT_DIR}MSStoreCLI);" + $env:PATH + } + $msstore_text = " | Installed MSStoreCLI: $(msstore --version)" + } +Write-Output "(INFO): Installed WinSDK: ${win_sdk_version}${msstore_text}" +Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_tlkt$([char]13)$([char]27)[0K" + + +# 2. GLOBAL VARIABLES +Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_info$([char]13)$([char]27)[0KGetting MSIX global info" +if (-not $build_dir) + { + $build_dir = Get-ChildItem _build* | Select-Object -First 1 + } +$config_path = "$build_dir\config.h" +if (-not (Test-Path "$config_path")) + { + Write-Host "(ERROR): config.h file not found. You can run 'build/windows/2_build-gimp-msys2.ps1' or configure GIMP to generate it." -ForegroundColor red + exit 1 + } +ForEach ($line in $(Select-String 'define' $config_path -AllMatches)) + { + Invoke-Expression $($line -replace '^.*#' -replace 'define ','$' -replace ' ','=') + } + +## Get Identity Name (the dir shown in Explorer) +if (-not $GIMP_RELEASE -or $GIMP_IS_RC_GIT) + { + $IDENTITY_NAME="GIMP.GIMPInsider" + } +elseif ($GIMP_RELEASE -and $GIMP_UNSTABLE -or $GIMP_RC_VERSION) + { + $IDENTITY_NAME="GIMP.GIMPPreview" + } +else + { + $IDENTITY_NAME="GIMP.43237F745459" + } + +## Get custom GIMP version (major.minor.micro+revision.0), a compliant way to publish to Partner Center: +## https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/app-package-requirements) +### Get GIMP app version (major.minor) +$major = ($GIMP_APP_VERSION.Split('.'))[0] +$minor = ($GIMP_APP_VERSION.Split('.'))[-1] +### Get GIMP micro version +$micro = ($GIMP_VERSION.Split('.'))[-1] -replace '(.+?)-.+','$1' +if ($micro -ne '0') + { + $micro_digit = $micro + } +### Get GIMP revision +if ($revision -match '[1-9]' -and $CI_PIPELINE_SOURCE -ne 'schedule') + { + $revision_text = ", revision: $revision" + } +elseif ($GIMP_RC_VERSION) + { + $revision = $GIMP_RC_VERSION + $revision_text = ", RC: $revision" + } +else + { + $wack = "$revision" + $revision = "0" + } +$CUSTOM_GIMP_VERSION = "$GIMP_APP_VERSION.${micro_digit}${revision}.0" +Write-Output "(INFO): Identity: $IDENTITY_NAME | Version: $CUSTOM_GIMP_VERSION (major: $major, minor: $minor, micro: ${micro}${revision_text})" + +## Autodetects what arch bundles will be packaged +if (-not (Test-Path "$arm64_bundle") -and -not (Test-Path "$x64_bundle")) + { + Write-Host "(ERROR): No bundle found. You can tweak 'build/windows/2_build-gimp-msys2.ps1' or configure GIMP with '-Dms-store=true' to make one." -ForegroundColor red + exit 1 + } +elseif ((Test-Path "$arm64_bundle") -and -not (Test-Path "$x64_bundle")) + { + Write-Output "(INFO): Arch: arm64" + $supported_archs = "$arm64_bundle" + } +elseif (-not (Test-Path "$arm64_bundle") -and (Test-Path "$x64_bundle")) + { + Write-Output "(INFO): Arch: x64" + $supported_archs = "$x64_bundle" + } +elseif ((Test-Path "$arm64_bundle") -and (Test-Path "$x64_bundle")) + { + Write-Output "(INFO): Arch: arm64 and x64" + $supported_archs = "$arm64_bundle","$x64_bundle" + $temp_text='temporary ' + } +Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_info$([char]13)$([char]27)[0K" + + +foreach ($bundle in $supported_archs) + { + if (("$bundle" -like '*a64*') -or ("$bundle" -like '*aarch64*') -or ("$bundle" -like '*arm64*')) + { + $msix_arch = 'arm64' + } + else + { + $msix_arch = 'x64' + } + ## Create temporary dir + if (Test-Path $msix_arch) + { + Remove-Item $msix_arch/ -Recurse + } + New-Item $msix_arch -ItemType Directory | Out-Null + ### Prevent Git going crazy + $ig_content = "`n$msix_arch" + if (Test-Path .gitignore -Type Leaf) + { + if (-not (Test-Path .gitignore.bak -Type Leaf)) + { + Copy-Item .gitignore .gitignore.bak + } + Add-Content .gitignore "$ig_content" + } + else + { + New-Item .gitignore | Out-Null + Set-Content .gitignore "$ig_content" + } + + + # 3. PREPARE MSIX "SOURCE" + Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_source[collapsed=true]$([char]13)$([char]27)[0KMaking assets for $msix_arch MSIX" + # (We test the existence of the icons here (and not on section 3.2.) to avoid creating AppxManifest.xml for nothing) + $icons_path = "$build_dir\build\windows\store\Assets" + if (-not (Test-Path "$icons_path")) + { + Write-Host "(ERROR): MS Store icons not found. You can tweak 'build/windows/2_build-gimp-msys2.ps1' or configure GIMP with '-Dms-store=true' to build them." -ForegroundColor red + exit 1 + } + + ## 3.1. CONFIGURE MANIFEST + Write-Output "(INFO): configuring AppxManifest.xml for $msix_arch" + Copy-Item build\windows\store\AppxManifest.xml $msix_arch + function conf_manifest ([string]$search, [string]$replace) + { + (Get-Content $msix_arch\AppxManifest.xml) | Foreach-Object {$_ -replace "$search","$replace"} | + Set-Content $msix_arch\AppxManifest.xml + } + ### Set msix_arch + conf_manifest 'neutral' "$msix_arch" + ### Set Identity Name + conf_manifest '@IDENTITY_NAME@' "$IDENTITY_NAME" + ### Set Display Name (the name shown in MS Store, on Start Menu etc) + if (-not $GIMP_RELEASE -or $GIMP_IS_RC_GIT) + { + $display_name='GIMP (Insider)' + } + elseif (($GIMP_RELEASE -and $GIMP_UNSTABLE) -or $GIMP_RC_VERSION) + { + $display_name='GIMP (Preview)' + } + else + { + $display_name='GIMP' + } + conf_manifest '@DISPLAY_NAME@' "$display_name" + ### Set custom GIMP version (major.minor.micro+revision.0) + conf_manifest '@CUSTOM_GIMP_VERSION@' "$CUSTOM_GIMP_VERSION" + #### Needed to differentiate on PowerShell etc + if ($GIMP_RELEASE -and -not $GIMP_IS_RC_GIT) + { + $mutex_suffix="-$GIMP_MUTEX_VERSION" + } + conf_manifest '@MUTEX_SUFFIX@' "$mutex_suffix" + ### List supported filetypes + $file_types = Get-Content "$build_dir\plug-ins\file_associations.list" | Foreach-Object {" ." + $_} | + Foreach-Object {$_ + ""} | Where-Object {$_ -notmatch 'xcf'} + (Get-Content $msix_arch\AppxManifest.xml) | Foreach-Object {$_ -replace "@FILE_TYPES@","$file_types"} | + Set-Content $msix_arch\AppxManifest.xml + + ## 3.2. CREATE ICON ASSETS + Write-Output "(INFO): generating resources*.pri from $icons_path" + ### Copy pre-generated icons to msix_arch\Assets + New-Item $msix_arch\Assets -ItemType Directory | Out-Null + Copy-Item "$icons_path\*.png" $msix_arch\Assets\ -Recurse + ### Generate temp priconfig.xml then resources*.pri + Set-Location $msix_arch + makepri createconfig /cf priconfig.xml /dq lang-en-US /pv 10.0.0 | Out-File ..\winsdk.log; if ("$LASTEXITCODE" -gt '0') { exit 1 } + Set-Location ..\ + makepri new /pr $msix_arch /cf $msix_arch\priconfig.xml /of $msix_arch | Out-File winsdk.log -Append; if ("$LASTEXITCODE" -gt '0') { exit 1 } + Remove-Item $msix_arch\priconfig.xml + Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_source$([char]13)$([char]27)[0K" + + + # 4. COPY GIMP FILES + Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_files[collapsed=true]$([char]13)$([char]27)[0KPreparing GIMP files in $msix_arch VFS" + $vfs = "$msix_arch\VFS\ProgramFilesX64\GIMP" + ## Copy files into VFS folder (to support external 3P plug-ins) + Copy-Item "$bundle" "$vfs" -Recurse -Force + + ## MSIX-specific adjustments (needed because we use the same gimp-* bundle as base to both .exe and .msix) + ### Set revision on about dialog (this does the same as '-Drevision' build option) + if (-not $GIMP_RC_VERSION) + { + (Get-Content "$vfs\share\gimp\*\gimp-release") | Foreach-Object {$_ -replace "revision=0","revision=$revision"} | + Set-Content "$vfs\share\gimp\*\gimp-release" + } + ### Disable Update check (ONLY FOR RELEASES) + if ($GIMP_RELEASE -and -not $GIMP_IS_RC_GIT) + { + Add-Content "$vfs\share\gimp\*\gimp-release" 'check-update=false' + } + + ## Parity adjustments (to make the .msix IDENTICAL TO THE .EXE INSTALLER, except for the adjustments above) + Get-ChildItem "$vfs" -Recurse -Include (".gitignore", "gimp.cmd") | Remove-Item -Recurse + Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_files$([char]13)$([char]27)[0K" + + + # 5.A. MAKE .MSIX AND CORRESPONDING .APPXSYM + Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_making[collapsed=true]$([char]13)$([char]27)[0KPackaging ${temp_text}$msix_arch MSIX" + ## Make .appxsym for each msix_arch (ONLY FOR RELEASES) + $APPXSYM = "${IDENTITY_NAME}_${CUSTOM_GIMP_VERSION}_$msix_arch.appxsym" + if ($CI_COMMIT_TAG -match 'GIMP_[0-9]*_[0-9]*_[0-9]*' -or $GIMP_CI_MS_STORE -like 'MSIXUPLOAD*') + { + Write-Output "(INFO): making $APPXSYM" + Get-ChildItem $msix_arch -Filter *.pdb -Recurse | Compress-Archive -DestinationPath "$APPXSYM.zip" + Rename-Item "$APPXSYM.zip" "$APPXSYM" + #(To not ship .pdb we need an online symbol server pointed on _NT_SYMBOL_PATH) + #Get-ChildItem $msix_arch -Include *.pdb -Recurse -Force | Remove-Item -Recurse -Force + } + + ## Make .msix from each msix_arch + $MSIX_ARTIFACT = $APPXSYM -replace '.appxsym','.msix' + Write-Output "(INFO): packaging $MSIX_ARTIFACT" + makeappx pack /d $msix_arch /p $MSIX_ARTIFACT /o | Out-File winsdk.log -Append; if ("$LASTEXITCODE" -gt '0') { exit 1 } + Remove-Item $msix_arch/ -Recurse + Remove-Item .gitignore + if (Test-Path .gitignore.bak -Type Leaf) + { + Rename-Item .gitignore.bak .gitignore + } + Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):${msix_arch}_making$([char]13)$([char]27)[0K" + } #END of 'foreach ($bundle' + + +# 5.B. MAKE .MSIXBUNDLE OR SUBSEQUENT .MSIXUPLOAD +if (((Test-Path $arm64_bundle) -and (Test-Path $x64_bundle)) -and (Get-ChildItem *.msix -Recurse).Count -gt 1) + { + $MSIXBUNDLE = "${IDENTITY_NAME}_${CUSTOM_GIMP_VERSION}_neutral.msixbundle" + $MSIX_ARTIFACT = "$MSIXBUNDLE" + if ($GIMP_RELEASE -and -not $GIMP_IS_RC_GIT) + { + $MSIXUPLOAD = "${IDENTITY_NAME}_${CUSTOM_GIMP_VERSION}_x64_arm64_bundle.msixupload" + $MSIX_ARTIFACT = "$MSIXUPLOAD" + } + Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_making[collapsed=true]$([char]13)$([char]27)[0KPackaging $MSIX_ARTIFACT" + + ## Make .msixbundle with all archs + ## (This is needed not only for easier multi-arch testing but + ## also to make sure against Partner Center getting confused) + New-Item _TempOutput -ItemType Directory | Out-Null + Move-Item *.msix _TempOutput/ + makeappx bundle /bv "${CUSTOM_GIMP_VERSION}" /d _TempOutput /p $MSIXBUNDLE /o; if ("$LASTEXITCODE" -gt '0') { exit 1 } + Remove-Item _TempOutput/ -Recurse + + ## Make .msixupload (ONLY FOR RELEASES) + if ($GIMP_RELEASE -and -not $GIMP_IS_RC_GIT) + { + Write-Output "(INFO): creating $MSIXUPLOAD for submission" + Compress-Archive -Path "*.appxsym","*.msixbundle" -DestinationPath "$MSIXUPLOAD.zip" + Rename-Item "$MSIXUPLOAD.zip" "$MSIXUPLOAD" + Remove-Item *.appxsym -Force + Remove-Item *.msixbundle -Force + } + Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_making$([char]13)$([char]27)[0K" + } + + +# 6.A. CERTIFY .MSIX OR .MSIXBUNDLE WITH WACK (OPTIONAL) +# (Partner Center does the same thing before publishing) +if (-not $GITLAB_CI -and $wack -eq 'WACK') + { + Write-Output "(INFO): certifying $MSIX_ARTIFACT with WACK" + ## Prepare file naming + ## (appcert CLI does NOT allow relative paths) + $fullpath = $PWD + ## (appcert CLI does NOT allow more than one dot on xml name) + $xml_artifact = "$MSIX_ARTIFACT" -replace '.msix', '-report.xml' -replace 'bundle', '' + + ## Generate detailed report + ## (appcert only works with admin rights so let's use sudo) + $nt_build = [System.Environment]::OSVersion.Version | Select-Object -ExpandProperty Build + if ($nt_build -lt '26052') + { + Write-Host "(ERROR): Certification from CLI requires 'sudo' (available only for build 10.0.26052.0 and above)" -ForegroundColor Red + exit 1 + } + $sudo_mode = Get-ItemProperty Registry::'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Sudo' | Select-Object -ExpandProperty Enabled + if ($sudo_mode -eq '0') + { + Write-Host "(ERROR): 'sudo' is disabled. Please enable it in Settings." -ForegroundColor Red + exit 1 + } + elseif ($sudo_mode -ne '3') + { + Write-Host "(ERROR): 'sudo' is not in normal/inline mode. Please change it in Settings." -ForegroundColor Red + exit 1 + } + sudo appcert test -appxpackagepath $fullpath\$MSIX_ARTIFACT -reportoutputpath $fullpath\$xml_artifact + + ## Output overall result + if (Test-Path $xml_artifact -Type Leaf) + { + $xmlObject = New-Object XML + $xmlObject.Load("$xml_artifact") + $result = $xmlObject.REPORT.OVERALL_RESULT + if ($result -eq 'FAIL') + { + Write-Host "(ERROR): $MSIX_ARTIFACT not passed. See: $xml_artifact" -ForegroundColor Red + exit 1 + } + elseif ($result -eq 'WARNING') + { + Write-Host "(WARNING): $MSIX_ARTIFACT passed partially. See: $xml_artifact" -ForegroundColor Yellow + } + #elseif ($result -eq 'PASS') + #{ + # Output nothing + #} + } + } + + +# 6.B. SIGN .MSIX OR .MSIXBUNDLE PACKAGE (NOT THE BINARIES) +# (Partner Center does the same thing, for free, before publishing) +if (-not $GIMP_RELEASE -or $GIMP_IS_RC_GIT) + { + Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_trust${msix_arch}[collapsed=true]$([char]13)$([char]27)[0KSelf-signing $MSIX_ARTIFACT (for testing purposes)" + ## Check if certificate for nightly builds is fine + $sign_output = & signtool sign /debug /fd sha256 /a /f $(Resolve-Path build\windows\store\pseudo-gimp*.pfx) /p eek $MSIX_ARTIFACT + Write-Output $sign_output + if ($sign_output -like "*After expiry filter, 0 certs were left*") + { + $pseudo_gimp = "pseudo-gimp_$(Get-Date -UFormat %s -Millisecond 0)" + New-SelfSignedCertificate -Type Custom -Subject "$(([xml](Get-Content build\windows\store\AppxManifest.xml)).Package.Identity.Publisher)" -KeyUsage DigitalSignature -FriendlyName "$pseudo_gimp" -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") | Out-Null + Export-PfxCertificate -Cert "Cert:\CurrentUser\My\$(Get-ChildItem Cert:\CurrentUser\My | Where-Object FriendlyName -EQ "$pseudo_gimp" | Select-Object -ExpandProperty Thumbprint)" -FilePath "${pseudo_gimp}.pfx" -Password (ConvertTo-SecureString -String eek -Force -AsPlainText) | Out-Null + Remove-Item "Cert:\CurrentUser\My\$(Get-ChildItem Cert:\CurrentUser\My | Where-Object FriendlyName -EQ "$pseudo_gimp" | Select-Object -ExpandProperty Thumbprint)" + Write-Host "(ERROR): Self-signing certificate expired. Please commit the generated '${pseudo_gimp}.pfx' on 'build\windows\store\'." -ForegroundColor red + $sha256_pfx = (Get-FileHash "${pseudo_gimp}.pfx" -Algorithm SHA256 | Select-Object -ExpandProperty Hash).ToLower() + Write-Output "(INFO): ${pseudo_gimp}.pfx SHA-256: $sha256_pfx" + } + else + { + Copy-Item build\windows\store\pseudo-gimp*.pfx pseudo-gimp.pfx -Recurse + + ## Generate checksums + $sha256 = (Get-FileHash $MSIX_ARTIFACT -Algorithm SHA256 | Select-Object -ExpandProperty Hash).ToLower() + Write-Output "(INFO): $MSIX_ARTIFACT SHA-256: $sha256" + $sha512 = (Get-FileHash $MSIX_ARTIFACT -Algorithm SHA512 | Select-Object -ExpandProperty Hash).ToLower() + Write-Output "(INFO): $MSIX_ARTIFACT SHA-512: $sha512" + } + + ## Check in advance if the CLIENT_SECRET we will use in the future tagged pipeline is fine + if ($CI_COMMIT_TAG) + { + $latest_msix_secret = New-Object -TypeName System.DateTime -ArgumentList 2026, 9, 20 + Write-Output "(INFO): CLIENT_SECRET expire date is: $latest_msix_secret" + if ((Get-Date) -ge $latest_msix_secret) + { + $expired_secret = "$latest_msix_secret" + Write-Host "(ERROR): Submission secret for releases expired. Please follow https://developer.gimp.org/core/maintainer/accounts/msstore/ then commit its expire date on 'build\windows\store\*.ps1'." -ForegroundColor red + } + } + Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_trust${msix_arch}$([char]13)$([char]27)[0K" + } + + +if ($GITLAB_CI) + { + # GitLab doesn't support wildcards when using "expose_as" so let's move to a dir + $OUTPUT_DIR = "$PWD\build\windows\store\_Output" + New-Item $OUTPUT_DIR -ItemType Directory -Force | Out-Null + if (-not $GIMP_RELEASE -or $GIMP_IS_RC_GIT) + { + Move-Item pseudo-gimp*.pfx $OUTPUT_DIR + } + if ($sha256_pfx -or $expired_secret) + { + exit 1 + } + Move-Item $MSIX_ARTIFACT $OUTPUT_DIR -Force + } + + +# 7. SUBMIT .MSIXUPLOAD TO MS STORE (ONLY FOR RELEASES) +if ("$CI_COMMIT_TAG" -eq (git describe --all | Foreach-Object {$_ -replace 'tags/',''})) + { + Write-Output "$([char]27)[0Ksection_start:$(Get-Date -UFormat %s -Millisecond 0):msix_submission[collapsed=true]$([char]13)$([char]27)[0KSubmitting $MSIX_ARTIFACT to Microsoft Store" + ## Needed credentials for submission + ### Connect with our Microsoft Entra credentials (stored on GitLab) + ### (The last one can be revoked at any time in MS Entra Admin center) + msstore reconfigure --tenantId $TENANT_ID --sellerId $SELLER_ID --clientId $CLIENT_ID --clientSecret $CLIENT_SECRET; if ("$LASTEXITCODE" -gt '0') { exit 1 } + ### Set product_id (which is not confidential) needed by HTTP calls of some commands + if ($GIMP_UNSTABLE -or $GIMP_RC_VERSION) + { + $env:PRODUCT_ID="9NZVDVP54JMR" + } + else + { + $env:PRODUCT_ID="9PNSJCLXDZ0V" + } + + ## Create submission and upload .msixupload file to it + msstore publish $OUTPUT_DIR\$MSIX_ARTIFACT -id $env:PRODUCT_ID -nc; if ("$LASTEXITCODE" -gt '0') { exit 1 } + + ## Update submission info (if PS6 or up. Check the section 1 of this script) + $env:GIMP_VERSION="$GIMP_VERSION" + pwsh -Command ` + { + $jsonObject = msstore submission get $env:PRODUCT_ID | ConvertFrom-Json -AsHashtable; if ("$LASTEXITCODE" -gt '0') { exit 1 } + ###Get changelog from Linux appdata + $xmlObject = New-Object XML + $xmlObject.Load("$PWD\desktop\org.gimp.GIMP.appdata.xml.in.in") + if ($xmlObject.component.releases.release[0].version -ne ("$env:GIMP_VERSION".ToLower() -replace '-','~')) + { + #This check is needed to ensure the right release notes etc when the submission is (rarely) done manually/locally + Write-Host "(WARNING): appdata does not match main meson file. Submission info can't be updated." -ForegroundColor yellow + exit 1 + } + $jsonObject."Listings"."en-us"."BaseListing".'ShortDescription' = ($xmlObject.component.summary).Trim() + $jsonObject."Listings"."en-us"."BaseListing".'Description' = ($xmlObject.component.description.SelectNodes(".//p") | ForEach-Object { ($_.InnerText).Trim() -replace '\s*\r?\n\s*', ' ' } ) -join "`n`n" + #NOTE: Submission API does not allow more than 1500 chars on ReleaseNotes so we skip some

    or

  • when needed + $jsonObject."Listings"."en-us"."BaseListing".'ReleaseNotes' = ($xmlObject.component.releases.release[0].description.SelectNodes(".//p | .//li") | ForEach-Object -Begin {$len=0} -Process { $text = ($_.InnerText).Trim() -replace '\s*\r?\n\s*', ' '; $formatted = if ($_.Name -eq 'li') { "- $text" } else { $text }; if (($len + $formatted.Length + 1) -lt 1490) { $len += $formatted.Length + 1; $formatted } }) -join "`n" + ###Send submission info + msstore submission updateMetadata $env:PRODUCT_ID ($jsonObject | ConvertTo-Json -Depth 100); if ("$LASTEXITCODE" -gt '0') { exit 1 } + } + + ## Start certification then publishing + msstore submission publish $env:PRODUCT_ID; if ("$LASTEXITCODE" -gt '0') { exit 1 } + Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):msix_submission$([char]13)$([char]27)[0K" + }