82e0ef17e9
ASP.NET AJAX / build_web_app (push) Waiting to run
Angular / build_angular (push) Waiting to run
ASP.NET Core (with Reporting) / build_windows (push) Waiting to run
Blazor (with Reporting) / build_windows (push) Waiting to run
Blazor (with Reporting) / build_linux (push) Waiting to run
Console (.NET) / build_console (arm64, linux) (push) Waiting to run
Console (.NET) / build_console (arm64, win) (push) Waiting to run
Console (.NET) / build_console (x64, linux) (push) Waiting to run
Console (.NET) / build_console (x64, win) (push) Waiting to run
MAUI / Windows Smoketest (push) Waiting to run
MAUI / Android Smoketest (push) Waiting to run
MAUI / iOS Smoketest (push) Waiting to run
MAUI / MacCatalyst Smoketest (push) Waiting to run
WinForms (.NET Framework) / build_desktop (Release, x64) (push) Waiting to run
WinForms (.NET Framework) / build_desktop (Release, x86) (push) Waiting to run
WinUI3 / build-windows (push) Waiting to run
WPF (.NET Framework) / build_desktop (Release, x64) (push) Waiting to run
WPF (.NET Framework) / build_desktop (Release, x86) (push) Waiting to run
ASP.NET Core (with Reporting) - Docker / Microsoft Base - Publish to Docker Hub (push) Waiting to run
ASP.NET Core (with Reporting) - Docker / CentOS Base - Publish to Docker Hub (push) Waiting to run
Blazor (with Reporting) - Docker / Dockerfile Build and Publish (push) Waiting to run
771 lines
36 KiB
YAML
771 lines
36 KiB
YAML
name: MAUI (Distribution)
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
|
|
defaults:
|
|
run:
|
|
shell: pwsh
|
|
|
|
permissions:
|
|
actions: write # Needed to delete artifacts after bundle upload
|
|
contents: write # Needed to create GitHub Releases
|
|
id-token: write # Needed for Azure auth (OIDC)
|
|
|
|
env:
|
|
# ____Global____________________________________________
|
|
APP_NAME: "MauiDemo"
|
|
PROJECT_PATH: "src/MAUI/MauiDemo.csproj"
|
|
PROJECT_DIRECTORY: "src/MAUI"
|
|
SDK_VERSION: '10.0.x'
|
|
NET_TFM: 'net10.0'
|
|
XCODE_VERSION: '26.4'
|
|
MAC_DOTNET_VERSION: '10.0.203'
|
|
NUGET_CONFIG_PATH: 'src/nuget.config'
|
|
TELERIK_NUGET_KEY: ${{secrets.TELERIK_NUGET_KEY}}
|
|
TELERIK_LICENSE: ${{secrets.TELERIK_LICENSE_KEY}}
|
|
# ____Android___________________________________________
|
|
ANDROID_ARTIFACTS_PATH: "artifacts_android"
|
|
ANDROID_KEYSTORE_PATH: "${{github.workspace}}/android-upload.keystore"
|
|
JAVA_VERSION: '17'
|
|
JAVA_DISTRIBUTION: 'microsoft'
|
|
# ____MacCatalyst_______________________________________
|
|
APPLE_DEV_ID_APP_CERT_NAME: "Developer ID Application: Lancelot Software, LLC (L65255N3F7)"
|
|
APPLE_DEV_ID_INSTALLER_CERT_NAME: "Developer ID Installer: Lancelot Software, LLC (L65255N3F7)"
|
|
APPLE_NOTARY_TEAM_ID: "L65255N3F7"
|
|
MAC_PACKAGE_NAME: "MauiDemo-MacCatalyst"
|
|
MAC_APP_BUNDLE_PATH: "src/Maui/bin/Release/net10.0-maccatalyst/MauiDemo.app"
|
|
MAC_ARTIFACTS_PATH: "artifacts/maccatalyst"
|
|
# ____iOS______________________________________________ (also check matrix vars)
|
|
APPLE_APP_ID: "com.lancelotsoftware.MauiDemoApp"
|
|
IOS_RID: ios-arm64
|
|
# ____Windows__________________________________________ (also check matrix vars)
|
|
WINDOWS_ARTIFACTS_PATH: "artifacts_windows"
|
|
SERVICE_PRINCIPAL_CLIENT_ID: "32daa13b-f4bb-4809-8ef6-58cb39051acd"
|
|
SERVICE_PRINCIPAL_TENANT_ID: "bd47e796-3473-4b8a-9101-1f4c0c7af31a"
|
|
SERVICE_PRINCIPAL_SUBSCRIPTION_ID: "48ab4839-62af-4ab3-afe6-043ea4d7c137"
|
|
SIGNING_ACCT_NAME: "PrimaryCodeSign"
|
|
SIGNING_ACCT_CERT_PROFILE: "lancemccarthylivepublic"
|
|
SIGNING_ACCT_ENDPOINT_URL: "https://eus.codesigning.azure.net/"
|
|
|
|
# ------------------ REQUIRED SECRETS ---------------- #
|
|
# ____Global____________________________________________
|
|
# TELERIK_NUGET_KEY
|
|
# TELERIK_LICENSE_KEY
|
|
# ____Android___________________________________________
|
|
# ANDROID_SIGNING_KEYSTORE_BASE64
|
|
# ANDROID_SIGNING_KEYSTORE_PASS
|
|
# ANDROID_SIGNING_KEY_ALIAS
|
|
# ANDROID_SIGNING_KEY_PASS
|
|
# ____MacCatalyst_______________________________________
|
|
# APPLE_DEVELOPER_ID_INSTALLER_CERT_BASE64
|
|
# APPLE_DEVELOPER_ID_INSTALLER_CERT_PASSWORD
|
|
# APPLE_DEVELOPER_ID_APPLICATION_CERT_BASE64
|
|
# APPLE_NOTARY_APPLE_ID
|
|
# APPLE_NOTARY_APP_PASSWORD
|
|
# ____iOS______________________________________________
|
|
# APPLE_DISTRIBUTION_CERT_BASE64
|
|
# APPLE_DISTRIBUTION_CERT_PASSWORD
|
|
# APPSTORE_API_ISSUER_ID
|
|
# APPSTORE_API_KEY_ID
|
|
# APPSTORE_API_PRIVATE_KEY
|
|
# ____Windows__________________________________________
|
|
# No additional secrets needed, uses Azure Trusted Signing.
|
|
|
|
|
|
jobs:
|
|
# ********************************************************************************** #
|
|
# Shared Resources #
|
|
# ********************************************************************************** #
|
|
shared-resources:
|
|
name: Create Shared Resources
|
|
runs-on: windows-latest
|
|
outputs:
|
|
app_version: ${{steps.version-creator.outputs.app_version}}
|
|
steps:
|
|
# Generates a version number using year.monthday.run_number (e.g., 2024.824.1)
|
|
- name: Generate version number using date and run number
|
|
id: version-creator
|
|
shell: pwsh
|
|
run: |
|
|
$buildDay = Get-Date -Format "yyyy.Mdd"
|
|
$runNumber = "$env:GITHUB_RUN_NUMBER"
|
|
$ver = $buildDay + "." + $runNumber
|
|
echo "app_version=$ver" >> $env:GITHUB_OUTPUT
|
|
|
|
|
|
# ********************************************************************************** #
|
|
# Android #
|
|
# ********************************************************************************** #
|
|
android:
|
|
name: Build Android (Store Upload)
|
|
runs-on: windows-latest
|
|
needs: shared-resources
|
|
if: ${{ success() && needs.shared-resources.outputs.app_version != '' }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Decode the Keystore
|
|
shell: pwsh
|
|
run: |
|
|
$file_bytes = [System.Convert]::FromBase64String("${{secrets.ANDROID_SIGNING_KEYSTORE_BASE64}}")
|
|
[IO.File]::WriteAllBytes("${{env.ANDROID_KEYSTORE_PATH}}", $file_bytes)
|
|
|
|
- name: Setup .NET SDK
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{env.SDK_VERSION}}
|
|
|
|
- uses: actions/setup-java@v5
|
|
with:
|
|
distribution: ${{env.JAVA_DISTRIBUTION}}
|
|
java-version: ${{env.JAVA_VERSION}}
|
|
|
|
- name: Install MAUI Workloads
|
|
run: dotnet workload install maui --source https://api.nuget.org/v3/index.json
|
|
|
|
- name: Set Telerik NuGet Credentials
|
|
run: dotnet nuget update source 'Telerik_v3_Feed' -s 'https://nuget.telerik.com/v3/index.json' -u 'api-key' -p "${{secrets.TELERIK_NUGET_KEY}}" --configfile '${{env.NUGET_CONFIG_PATH}}' --store-password-in-clear-text
|
|
|
|
- name: Restore NuGet packages
|
|
run: dotnet restore ${{env.PROJECT_PATH}} --configfile ${{env.NUGET_CONFIG_PATH}}
|
|
|
|
- name: Publish MAUI Android
|
|
run: |
|
|
dotnet publish ${{env.PROJECT_PATH}} -c Release -f ${{env.NET_TFM}}-android /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=${{env.ANDROID_KEYSTORE_PATH}} /p:AndroidSigningStorePass=${{secrets.ANDROID_SIGNING_KEYSTORE_PASS}} /p:AndroidSigningKeyAlias=${{secrets.ANDROID_SIGNING_KEY_ALIAS}} /p:AndroidSigningKeyPass=${{secrets.ANDROID_SIGNING_KEY_PASS}}
|
|
|
|
- name: Create artifacts folder
|
|
run: |
|
|
New-Item -ItemType Directory -Force -Path ${{env.ANDROID_ARTIFACTS_PATH}} | Out-Null
|
|
|
|
- name: Copy signed APKs & AABs
|
|
run: |
|
|
$publishRoot = "src/Maui/bin/Release/${{env.NET_TFM}}-android"
|
|
$apkFiles = Get-ChildItem -Path $publishRoot -Filter *-Signed.apk -File -Recurse -ErrorAction SilentlyContinue
|
|
$aabFiles = Get-ChildItem -Path $publishRoot -Filter *-Signed.aab -File -Recurse -ErrorAction SilentlyContinue
|
|
|
|
if ($apkFiles.Count -eq 0 -and $aabFiles.Count -eq 0) {
|
|
throw "No signed Android APK/AAB files found under $publishRoot"
|
|
}
|
|
|
|
$apkFiles | ForEach-Object { Copy-Item -Path $_.FullName -Destination ${{env.ANDROID_ARTIFACTS_PATH}} -Force }
|
|
$aabFiles | ForEach-Object { Copy-Item -Path $_.FullName -Destination ${{env.ANDROID_ARTIFACTS_PATH}} -Force }
|
|
|
|
- name: Publish Android build artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: "${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}_signed-android"
|
|
path: "${{env.ANDROID_ARTIFACTS_PATH}}/*"
|
|
if-no-files-found: error
|
|
retention-days: 60
|
|
|
|
|
|
# ********************************************************************************** #
|
|
# Windows (Sideload - msixbundle) #
|
|
# ********************************************************************************** #
|
|
windows-sideload-packages:
|
|
name: Build Windows (Sideload)
|
|
needs: shared-resources
|
|
runs-on: windows-latest
|
|
if: ${{ needs.shared-resources.outputs.app_version != ''}}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: x64
|
|
runtime_id: win-x64
|
|
platform: x64
|
|
- arch: arm64
|
|
runtime_id: win-arm64
|
|
platform: ARM64
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup .NET Core SDK
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{env.SDK_VERSION}}
|
|
|
|
# Needed only for WinUI builds
|
|
- name: Add msbuild to PATH
|
|
uses: microsoft/setup-msbuild@v3
|
|
|
|
- name: Install MAUI workloads
|
|
run: dotnet workload install maui --source https://api.nuget.org/v3/index.json
|
|
|
|
- name: Set Telerik NuGet Credentials
|
|
run: dotnet nuget update source 'Telerik_v3_Feed' -s 'https://nuget.telerik.com/v3/index.json' -u 'api-key' -p "${{secrets.TELERIK_NUGET_KEY}}" --configfile '${{env.NUGET_CONFIG_PATH}}' --store-password-in-clear-text
|
|
|
|
- name: Restore NuGet packages
|
|
run: dotnet restore ${{env.PROJECT_PATH}} --configfile ${{env.NUGET_CONFIG_PATH}}
|
|
|
|
- name: Update manifest for sideload build
|
|
run: |
|
|
[xml]$manifest = Get-Content '${{env.PROJECT_DIRECTORY}}\Platforms\Windows\Package.appxmanifest'
|
|
$manifest.Package.Identity.Version = "${{needs.shared-resources.outputs.app_version}}.0"
|
|
$manifest.Save('${{env.PROJECT_DIRECTORY}}\Platforms\Windows\Package.appxmanifest')
|
|
|
|
- name: Publish ${{matrix.arch}} Windows package
|
|
id: publish-package
|
|
run: |
|
|
$appVersion = "${{needs.shared-resources.outputs.app_version}}.0"
|
|
$artifactDir = "${{github.workspace}}\${{env.WINDOWS_ARTIFACTS_PATH}}\${{matrix.arch}}"
|
|
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
|
Write-Host "Publishing Windows ${{matrix.arch}}..."
|
|
$publishArgs = @(
|
|
'publish'
|
|
'${{env.PROJECT_PATH}}'
|
|
'-c', 'Release'
|
|
'-f', '${{env.NET_TFM}}-windows10.0.19041.0'
|
|
'-p:Platform=${{matrix.platform}}'
|
|
'-p:AppxPackageBuildPlatform=${{matrix.platform}}'
|
|
'-p:RuntimeIdentifierOverride=${{matrix.runtime_id}}'
|
|
'-p:GenerateAppxPackageOnBuild=true'
|
|
'-p:AppxPackageSigningEnabled=false'
|
|
'-p:UapAppxPackageBuildMode=SideloadOnly'
|
|
'-p:AppxBundle=Never'
|
|
'-p:WindowsPackageType=MSIX'
|
|
)
|
|
& dotnet @publishArgs
|
|
if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed for ${{matrix.arch}} with exit code $LASTEXITCODE" }
|
|
# MAUI writes MSIX to src\${{env.APP_NAME}}\AppPackages\<name>_<ver>_<arch>_Test\*.msix
|
|
$appPackagesRoot = "src\${{env.APP_NAME}}\AppPackages"
|
|
$candidates = Get-ChildItem -Path $appPackagesRoot -Filter *.msix -Recurse -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -notmatch '\\Dependencies\\' -and $_.Name -match "_${{matrix.arch}}\.msix$" }
|
|
Write-Host "Found $($candidates.Count) candidate MSIX file(s) for ${{matrix.arch}}:"
|
|
$candidates | ForEach-Object { Write-Host " $($_.FullName)" }
|
|
$msix = $candidates | Sort-Object LastWriteTime -Descending | Select-Object -ExpandProperty FullName -First 1
|
|
if (-not $msix) { throw "No .msix found after publish for ${{matrix.arch}}" }
|
|
Write-Host "Found ${{matrix.arch}} package: $msix"
|
|
Copy-Item -Path $msix -Destination "$artifactDir\${{env.APP_NAME}}.${{matrix.arch}}.msix" -Force
|
|
echo "PACKAGE_PATH=$artifactDir\${{env.APP_NAME}}.${{matrix.arch}}.msix" >> $env:GITHUB_OUTPUT
|
|
|
|
- name: Upload ${{matrix.arch}} artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: "${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}_${{matrix.runtime_id}}"
|
|
path: ${{steps.publish-package.outputs.PACKAGE_PATH}}
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
windows-generate-msixbundle:
|
|
name: Bundle MSIX packages
|
|
needs: [shared-resources, windows-sideload-packages]
|
|
runs-on: windows-latest
|
|
steps:
|
|
- name: Clean bundle workspace
|
|
run: |
|
|
Remove-Item -Recurse -Force "${{github.workspace}}\bundle-input" -ErrorAction SilentlyContinue
|
|
Remove-Item -Recurse -Force "${{github.workspace}}\bundle-output" -ErrorAction SilentlyContinue
|
|
|
|
- name: Download x64 artifact
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: "${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}_win-x64"
|
|
path: ${{github.workspace}}\bundle-input
|
|
|
|
- name: Download arm64 artifact
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: "${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}_win-arm64"
|
|
path: ${{github.workspace}}\bundle-input
|
|
|
|
- name: Create MSIX bundle
|
|
id: bundle
|
|
run: |
|
|
$appVersion = "${{needs.shared-resources.outputs.app_version}}.0"
|
|
$inputDir = "${{github.workspace}}\bundle-input"
|
|
$outputDir = "${{github.workspace}}\bundle-output"
|
|
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
|
|
# Locate makeappx.exe from the latest installed Windows 10 SDK
|
|
$sdkRoot = "C:\Program Files (x86)\Windows Kits\10\bin"
|
|
$makeAppx = Get-ChildItem -Path $sdkRoot -Recurse -Filter makeappx.exe -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -match '\\x64\\makeappx\.exe$' } |
|
|
Sort-Object FullName -Descending | Select-Object -First 1
|
|
if (-not $makeAppx) { throw "makeappx.exe not found under $sdkRoot" }
|
|
Write-Host "Using makeappx: $($makeAppx.FullName)"
|
|
Get-ChildItem $inputDir -Filter *.msix | ForEach-Object { Write-Host "Input: $($_.FullName)" }
|
|
$bundlePath = "$outputDir\${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}.msixbundle"
|
|
& $makeAppx.FullName bundle /d $inputDir /p $bundlePath /bv $appVersion /o
|
|
if ($LASTEXITCODE -ne 0) { throw "makeappx bundle failed with exit code $LASTEXITCODE" }
|
|
|
|
echo "BUNDLE_PATH=$bundlePath" >> $env:GITHUB_OUTPUT
|
|
|
|
# Entra ID App Registration (Akeyless OIDC Provider) > Certificates and Secrets > Federated Credentials
|
|
- name: Azure login using OIDC via GitHub
|
|
uses: azure/login@v3
|
|
id: azlogin
|
|
with:
|
|
client-id: ${{env.SERVICE_PRINCIPAL_CLIENT_ID}}
|
|
tenant-id: ${{env.SERVICE_PRINCIPAL_TENANT_ID}}
|
|
subscription-id: ${{env.SERVICE_PRINCIPAL_SUBSCRIPTION_ID}}
|
|
|
|
- name: Sign MSIX bundle
|
|
uses: azure/trusted-signing-action@v1.2.0
|
|
with:
|
|
endpoint: ${{env.SIGNING_ACCT_ENDPOINT_URL}}
|
|
signing-account-name: ${{env.SIGNING_ACCT_NAME}}
|
|
certificate-profile-name: ${{env.SIGNING_ACCT_CERT_PROFILE}}
|
|
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
|
timestamp-digest: SHA256
|
|
file-digest: SHA256
|
|
files: ${{steps.bundle.outputs.BUNDLE_PATH}}
|
|
exclude-azure-cli-credential: false
|
|
exclude-environment-credential: true
|
|
exclude-workload-identity-credential: true
|
|
exclude-managed-identity-credential: true
|
|
exclude-shared-token-cache-credential: true
|
|
exclude-visual-studio-credential: true
|
|
exclude-visual-studio-code-credential: true
|
|
exclude-azure-powershell-credential: true
|
|
exclude-azure-developer-cli-credential: true
|
|
exclude-interactive-browser-credential: true
|
|
|
|
- name: Upload MSIX bundle artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: "${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}_signed-windows.msixbundle"
|
|
path: ${{steps.bundle.outputs.BUNDLE_PATH}}
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
|
|
# ********************************************************************************** #
|
|
# Windows (Store - msixupload) #
|
|
# ********************************************************************************** #
|
|
windows-store:
|
|
name: Build Windows (Store Upload)
|
|
runs-on: windows-latest
|
|
needs: shared-resources
|
|
if: ${{ success() && needs.shared-resources.outputs.app_version != '' }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Setup .NET SDK
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{env.SDK_VERSION}}
|
|
|
|
- name: Add msbuild to PATH
|
|
uses: microsoft/setup-msbuild@v3
|
|
|
|
- name: Install MAUI Workloads
|
|
run: dotnet workload install maui --source https://api.nuget.org/v3/index.json
|
|
|
|
- name: Set Telerik NuGet Credentials
|
|
run: dotnet nuget update source 'Telerik_v3_Feed' -s 'https://nuget.telerik.com/v3/index.json' -u 'api-key' -p "${{secrets.TELERIK_NUGET_KEY}}" --configfile '${{env.NUGET_CONFIG_PATH}}' --store-password-in-clear-text
|
|
|
|
- name: Restore NuGet packages
|
|
run: dotnet restore ${{env.PROJECT_PATH}} --configfile ${{env.NUGET_CONFIG_PATH}}
|
|
|
|
- name: Update manifest for store build
|
|
run: |
|
|
[xml]$manifest = Get-Content '${{env.PROJECT_DIRECTORY}}\Platforms\Windows\Package.appxmanifest'
|
|
$manifest.Package.Identity.Version = "${{needs.shared-resources.outputs.app_version}}.0"
|
|
$manifest.Save('${{env.PROJECT_DIRECTORY}}\Platforms\Windows\Package.appxmanifest')
|
|
|
|
- name: Build Maui WinUI project
|
|
run: dotnet publish ${{env.PROJECT_PATH}} -c Release -f ${{env.NET_TFM}}-windows10.0.19041.0 -p:AppxPackageSigningEnabled=false -p:AppxSymbolPackageEnabled=false -p:UapAppxPackageBuildMode=StoreUpload -p:AppxBundle=Always "-p:AppxBundlePlatforms=x64|arm64" -p:PlatformTarget=AnyCPU
|
|
|
|
- name: Publish build artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: "${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}_storeupload-windows"
|
|
path: "**/*.msixupload"
|
|
if-no-files-found: error
|
|
retention-days: 60
|
|
|
|
|
|
# ********************************************************************************** #
|
|
# MacCatalyst #
|
|
# ********************************************************************************** #
|
|
maccatalyst:
|
|
name: Build MacCatalyst (Signed & Notiarized)
|
|
runs-on: macos-26 # https://github.com/actions/runner-images#available-images
|
|
needs: shared-resources
|
|
if: ${{ success() && needs.shared-resources.outputs.app_version != '' }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: maxim-lobanov/setup-xcode@v1
|
|
with:
|
|
xcode-version: ${{env.XCODE_VERSION}}
|
|
|
|
- name: Setup .NET SDK
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{env.SDK_VERSION}}
|
|
|
|
- name: Install MAUI Workloads
|
|
run: dotnet workload install maui --source https://api.nuget.org/v3/index.json
|
|
|
|
- name: Set Telerik NuGet Credentials
|
|
run: dotnet nuget update source 'Telerik_v3_Feed' -s 'https://nuget.telerik.com/v3/index.json' -u 'api-key' -p "${{secrets.TELERIK_NUGET_KEY}}" --configfile '${{env.NUGET_CONFIG_PATH}}' --store-password-in-clear-text
|
|
|
|
- name: Restore NuGet packages
|
|
run: dotnet restore ${{env.PROJECT_PATH}} --configfile ${{env.NUGET_CONFIG_PATH}}
|
|
|
|
- name: Import Developer ID Installer Certificates
|
|
uses: Apple-Actions/import-codesign-certs@v7
|
|
with:
|
|
p12-file-base64: "${{secrets.APPLE_DEVELOPER_ID_INSTALLER_CERT_BASE64}}"
|
|
p12-password: "${{secrets.APPLE_DEVELOPER_ID_INSTALLER_CERT_PASSWORD}}"
|
|
keychain: installer_signing_temp
|
|
|
|
- name: Import Developer ID Application Certificates
|
|
uses: Apple-Actions/import-codesign-certs@v7
|
|
with:
|
|
p12-file-base64: "${{secrets.APPLE_DEVELOPER_ID_APPLICATION_CERT_BASE64}}"
|
|
p12-password: "${{secrets.APPLE_DEVELOPER_ID_APPLICATION_CERT_PASSWORD}}"
|
|
keychain: application_signing_temp
|
|
|
|
# Each import-codesign-certs call rewrites the keychain search list. The second import step (Application cert) drops the first (Installer) keychain, so product build cannot find the "Developer ID Installer" identity. This step re-adds both keychains explicitly before any signing occurs.
|
|
- name: Ensure both signing keychains are in the search list
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
security list-keychains -d user -s "$HOME/Library/Keychains/installer_signing_temp.keychain-db" "$HOME/Library/Keychains/application_signing_temp.keychain-db" "$HOME/Library/Keychains/login.keychain-db"
|
|
echo "Keychain search list:"
|
|
security list-keychains -d user
|
|
|
|
# Finally make sure both certs are available before we build
|
|
- name: Verify Developer ID identities are available
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
APPLE_DEV_ID_APP_CERT_NAME="${{env.APPLE_DEV_ID_APP_CERT_NAME}}"
|
|
APPLE_DEV_ID_INSTALLER_CERT_NAME="${{env.APPLE_DEV_ID_INSTALLER_CERT_NAME}}"
|
|
|
|
security find-identity -v -p codesigning
|
|
security find-certificate -a -c "$APPLE_DEV_ID_INSTALLER_CERT_NAME" || true
|
|
|
|
if ! security find-identity -v -p codesigning | grep -F "$APPLE_DEV_ID_APP_CERT_NAME" >/dev/null; then
|
|
echo "Missing Developer ID Application identity: $APPLE_DEV_ID_APP_CERT_NAME"
|
|
exit 1
|
|
fi
|
|
|
|
if ! security find-certificate -a -c "$APPLE_DEV_ID_INSTALLER_CERT_NAME" >/dev/null; then
|
|
echo "Missing Developer ID Installer certificate: $APPLE_DEV_ID_INSTALLER_CERT_NAME"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Build and sign MacCatalyst artifacts
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
cd "$GITHUB_WORKSPACE"
|
|
|
|
if [[ ! -f "$PROJECT_PATH" ]]; then
|
|
echo "Project file not found: $PROJECT_PATH"
|
|
echo "Current directory: $(pwd)"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$ARTIFACTS_DIR"
|
|
|
|
echo "Cleaning project..."
|
|
dotnet clean "$PROJECT_PATH" -c "$CONFIGURATION" -f "$TFM"
|
|
|
|
publish_args=(
|
|
"$PROJECT_PATH"
|
|
-c "$CONFIGURATION"
|
|
-f "$TFM"
|
|
"-p:CodesignKey=\"$CODESIGN_KEY\""
|
|
"-p:ApplicationVersion=$APP_VERSION"
|
|
"-p:ApplicationDisplayVersion=$APP_VERSION"
|
|
-p:UseHardenedRuntime=true
|
|
)
|
|
|
|
echo "Publishing project..."
|
|
dotnet publish "${publish_args[@]}"
|
|
|
|
if [[ ! -d "$APP_BUNDLE_PATH" ]]; then
|
|
echo "Expected app bundle not found at $APP_BUNDLE_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Verifying app signature..."
|
|
codesign -dv --verbose=2 "$APP_BUNDLE_PATH" >/dev/null 2>&1
|
|
|
|
echo "Creating signed app zip and installer pkg..."
|
|
rm -f "$SIGNED_ZIP_PATH" "$SIGNED_PKG_PATH"
|
|
ditto -c -k --sequesterRsrc --keepParent "$APP_BUNDLE_PATH" "$SIGNED_ZIP_PATH"
|
|
productbuild --component "$APP_BUNDLE_PATH" /Applications --sign "$INSTALLER_SIGN_ID" "$SIGNED_PKG_PATH"
|
|
|
|
echo "Done. Signed artifacts:"
|
|
echo "- $SIGNED_PKG_PATH"
|
|
echo "- $SIGNED_ZIP_PATH"
|
|
env:
|
|
PROJECT_PATH: "${{env.PROJECT_PATH}}"
|
|
TFM: "${{env.NET_TFM}}-maccatalyst"
|
|
CONFIGURATION: "Release"
|
|
APP_VERSION: "${{needs.shared-resources.outputs.app_version}}"
|
|
ARTIFACTS_DIR: "${{env.MAC_ARTIFACTS_PATH}}"
|
|
APP_BUNDLE_PATH: "${{env.MAC_APP_BUNDLE_PATH}}"
|
|
CODESIGN_KEY: "${{env.APPLE_DEV_ID_APP_CERT_NAME}}"
|
|
INSTALLER_SIGN_ID: "${{ env.APPLE_DEV_ID_INSTALLER_CERT_NAME}}"
|
|
SIGNED_ZIP_PATH: "$ARTIFACTS_DIR/${{env.MAC_PACKAGE_NAME}}-Release-signed.zip"
|
|
SIGNED_PKG_PATH: "$ARTIFACTS_DIR/${{env.MAC_PACKAGE_NAME}}-Release-signed.pkg"
|
|
|
|
- name: Notarize and staple MacCatalyst artifacts
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
cd "$GITHUB_WORKSPACE"
|
|
|
|
if [[ -z "$APPLE_NOTARY_APPLE_ID" || -z "$APPLE_NOTARY_APP_PASSWORD" || -z "$APPLE_NOTARY_TEAM_ID" ]]; then
|
|
echo "Missing notarization credentials."
|
|
echo "Set APPLE_NOTARY_APPLE_ID, APPLE_NOTARY_APP_PASSWORD, and APPLE_NOTARY_TEAM_ID."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d "$APP_BUNDLE_PATH" ]]; then
|
|
echo "Expected app bundle not found at $APP_BUNDLE_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$SIGNED_PKG_PATH" || ! -f "$SIGNED_ZIP_PATH" ]]; then
|
|
echo "Expected signed artifacts not found in $ARTIFACTS_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Submitting pkg for notarization..."
|
|
pkg_submit_json="$(xcrun notarytool submit "$SIGNED_PKG_PATH" --apple-id "$APPLE_NOTARY_APPLE_ID" --team-id "$APPLE_NOTARY_TEAM_ID" --password "$APPLE_NOTARY_APP_PASSWORD" --wait --output-format json)"
|
|
echo "$pkg_submit_json"
|
|
if ! grep -Eq '"status"[[:space:]]*:[[:space:]]*"Accepted"' <<< "$pkg_submit_json"; then
|
|
echo "Notarization failed for pkg."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Stapling and validating pkg..."
|
|
xcrun stapler staple "$SIGNED_PKG_PATH"
|
|
xcrun stapler validate "$SIGNED_PKG_PATH"
|
|
|
|
echo "Submitting signed app zip for notarization..."
|
|
zip_submit_json="$(xcrun notarytool submit "$SIGNED_ZIP_PATH" --apple-id "$APPLE_NOTARY_APPLE_ID" --team-id "$APPLE_NOTARY_TEAM_ID" --password "$APPLE_NOTARY_APP_PASSWORD" --wait --output-format json)"
|
|
echo "$zip_submit_json"
|
|
if ! grep -Eq '"status"[[:space:]]*:[[:space:]]*"Accepted"' <<< "$zip_submit_json"; then
|
|
echo "Notarization failed for app zip."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Stapling and validating app..."
|
|
xcrun stapler staple "$APP_BUNDLE_PATH"
|
|
xcrun stapler validate "$APP_BUNDLE_PATH"
|
|
|
|
# Recreate the final distributable zip from the stapled app.
|
|
ditto -c -k --sequesterRsrc --keepParent "$APP_BUNDLE_PATH" "$NOTARIZED_ZIP_PATH"
|
|
|
|
echo "Gatekeeper checks..."
|
|
spctl -a -t exec -vv "$APP_BUNDLE_PATH"
|
|
spctl -a -t install -vv "$SIGNED_PKG_PATH"
|
|
|
|
echo "Done. Artifacts:"
|
|
echo "- $SIGNED_PKG_PATH"
|
|
echo "- $NOTARIZED_ZIP_PATH"
|
|
env:
|
|
ARTIFACTS_DIR: "${{env.MAC_ARTIFACTS_PATH}}"
|
|
APP_BUNDLE_PATH: "${{env.MAC_APP_BUNDLE_PATH}}"
|
|
APPLE_NOTARY_APPLE_ID: "${{secrets.APPLE_NOTARY_APPLE_ID}}"
|
|
APPLE_NOTARY_APP_PASSWORD: "${{secrets.APPLE_NOTARY_APP_PASSWORD}}"
|
|
APPLE_NOTARY_TEAM_ID: "${{env.APPLE_NOTARY_TEAM_ID}}"
|
|
SIGNED_ZIP_PATH: "${{env.MAC_ARTIFACTS_PATH}}/${{env.MAC_PACKAGE_NAME}}-Release-signed.zip"
|
|
NOTARIZED_ZIP_PATH: "${{env.MAC_ARTIFACTS_PATH}}/${{env.MAC_PACKAGE_NAME}}-Release-notarized.zip"
|
|
SIGNED_PKG_PATH: "${{env.MAC_ARTIFACTS_PATH}}/${{env.MAC_PACKAGE_NAME}}-Release-signed.pkg"
|
|
|
|
- name: Publish MacCatalyst build artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: "${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}_signed-maccatalyst"
|
|
path: ${{env.MAC_ARTIFACTS_PATH}}/${{env.MAC_PACKAGE_NAME}}-Release-signed.pkg
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
|
|
# ********************************************************************************** #
|
|
# iOS #
|
|
# ********************************************************************************** #
|
|
ios:
|
|
name: Build iOS (${{matrix.distribution_name}})
|
|
runs-on: macos-26 # https://github.com/actions/runner-images#available-images
|
|
needs: shared-resources
|
|
if: ${{ success() && needs.shared-resources.outputs.app_version != '' }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- distribution_name: Store Upload IPA
|
|
artifact_suffix: storeupload-ios
|
|
provisioning_profile: MauiDemo_AppStore_Distribution
|
|
provisioning_profile_type: IOS_APP_STORE
|
|
rid: ios-arm64
|
|
- distribution_name: Ad Hoc Sideload IPA
|
|
artifact_suffix: sideload-ios
|
|
provisioning_profile: MauiDemo_AdHoc_Distribution
|
|
provisioning_profile_type: IOS_APP_ADHOC
|
|
rid: ios-arm64
|
|
env:
|
|
APPLE_PROV_PROFILE: ${{matrix.provisioning_profile}}
|
|
APPLE_PROV_PROFILE_TYPE: ${{matrix.provisioning_profile_type}}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: maxim-lobanov/setup-xcode@v1
|
|
with:
|
|
xcode-version: ${{env.XCODE_VERSION}}
|
|
|
|
- name: Setup .NET SDK
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{env.SDK_VERSION}}
|
|
|
|
- name: Install MAUI Workloads
|
|
run: dotnet workload install maui --source https://api.nuget.org/v3/index.json
|
|
|
|
- name: Set Telerik NuGet Credentials
|
|
run: dotnet nuget update source 'Telerik_v3_Feed' -s 'https://nuget.telerik.com/v3/index.json' -u 'api-key' -p "${{secrets.TELERIK_NUGET_KEY}}" --configfile '${{env.NUGET_CONFIG_PATH}}' --store-password-in-clear-text
|
|
|
|
- name: Restore NuGet packages
|
|
run: dotnet restore ${{env.PROJECT_PATH}} --configfile ${{env.NUGET_CONFIG_PATH}}
|
|
|
|
# Docs https://github.com/Apple-Actions/import-codesign-certs
|
|
- name: Import Code-Signing Certificates
|
|
uses: Apple-Actions/import-codesign-certs@v7
|
|
with:
|
|
p12-file-base64: "${{secrets.APPLE_DISTRIBUTION_CERT_BASE64}}"
|
|
p12-password: "${{secrets.APPLE_DISTRIBUTION_CERT_PASSWORD}}"
|
|
|
|
# Docs https://github.com/Apple-Actions/download-provisioning-profiles
|
|
- id: provisioning-profiles
|
|
uses: Apple-Actions/download-provisioning-profiles@v6
|
|
with:
|
|
profile-type: "${{env.APPLE_PROV_PROFILE_TYPE}}"
|
|
bundle-id: "${{env.APPLE_APP_ID}}"
|
|
issuer-id: "${{secrets.APPSTORE_API_ISSUER_ID}}"
|
|
api-key-id: "${{secrets.APPSTORE_API_KEY_ID}}"
|
|
api-private-key: "${{secrets.APPSTORE_API_PRIVATE_KEY}}"
|
|
|
|
- name: Verify provisioning profile
|
|
run: |
|
|
$profiles = '${{steps.provisioning-profiles.outputs.profiles}}' | ConvertFrom-Json
|
|
$profile = $profiles | Where-Object { $_.name -eq $env:APPLE_PROV_PROFILE -and $_.type -eq $env:APPLE_PROV_PROFILE_TYPE } | Select-Object -First 1
|
|
|
|
if ($null -eq $profile) {
|
|
$profiles | Format-Table -AutoSize | Out-String | Write-Host
|
|
throw "Provisioning profile '$env:APPLE_PROV_PROFILE' with type '$env:APPLE_PROV_PROFILE_TYPE' was not downloaded."
|
|
}
|
|
|
|
- name: Verify iOS signing identity is available
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
security find-identity -v -p codesigning
|
|
if ! security find-identity -v -p codesigning | grep -F "Apple Distribution" >/dev/null; then
|
|
echo "Missing Apple Distribution identity"
|
|
exit 1
|
|
fi
|
|
|
|
# Docs https://learn.microsoft.com/en-us/dotnet/maui/ios/deployment/publish-cli?view=net-maui-9.0
|
|
- name: Publish MAUI iOS IPA
|
|
run: |
|
|
# Query the actual signing identity to avoid comma parsing issues
|
|
$identityOutput = security find-identity -v -p codesigning | Select-String "Apple Distribution"
|
|
if (-not $identityOutput) { throw "No Apple Distribution identity found in keychain" }
|
|
$codesignKey = [regex]::Match($identityOutput.Line, '"(?<name>Apple Distribution:[^"]+)"').Groups['name'].Value
|
|
if (-not $codesignKey) { throw "Could not parse Apple Distribution identity from keychain output" }
|
|
Write-Host "Using codesign key: $codesignKey"
|
|
$quotedCodesignKey = '"' + $codesignKey + '"'
|
|
|
|
$publishArgs = @(
|
|
'publish'
|
|
'${{env.PROJECT_PATH}}'
|
|
'-f', '${{env.NET_TFM}}-ios'
|
|
'-c', 'Release'
|
|
'-p:ArchiveOnBuild=true'
|
|
'-p:RuntimeIdentifier=${{matrix.rid}}'
|
|
'-p:MtouchLink=SdkOnly'
|
|
'-p:ApplicationId=${{env.APPLE_APP_ID}}'
|
|
'-p:ApplicationVersion=${{needs.shared-resources.outputs.app_version}}'
|
|
'-p:CodesignProvision=${{env.APPLE_PROV_PROFILE}}'
|
|
"-p:CodesignKey=$quotedCodesignKey"
|
|
)
|
|
& dotnet @publishArgs
|
|
if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed with exit code $LASTEXITCODE" }
|
|
|
|
- name: Publish iOS build artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: "${{env.APP_NAME}}_v${{needs.shared-resources.outputs.app_version}}_${{matrix.artifact_suffix}}"
|
|
path: "${{env.PROJECT_DIRECTORY}}/bin/Release/${{env.NET_TFM}}-ios/${{matrix.rid}}/publish/*.ipa"
|
|
if-no-files-found: error
|
|
retention-days: 60
|
|
|
|
|
|
# ********************************************************************************** #
|
|
# GitHub Release #
|
|
# ********************************************************************************** #
|
|
# create-release:
|
|
# name: Create GitHub Release
|
|
# runs-on: ubuntu-latest
|
|
# needs: [shared-resources, android, windows-sideload-packages, windows-generate-msixbundle, windows-store, maccatalyst, ios]
|
|
# steps:
|
|
# - name: Download all artifacts
|
|
# uses: actions/download-artifact@v8
|
|
# with:
|
|
# path: release-artifacts
|
|
|
|
# - name: List downloaded artifacts
|
|
# shell: bash
|
|
# run: find release-artifacts -type f | sort
|
|
|
|
# - name: Prepare release files
|
|
# shell: bash
|
|
# run: |
|
|
# set -euo pipefail
|
|
# VER="${{needs.shared-resources.outputs.app_version}}"
|
|
# PREFIX="${{env.APP_NAME}}_v${VER}"
|
|
# mkdir -p release-upload
|
|
# copy_one() {
|
|
# local artifact_dir="$1"
|
|
# local pattern="$2"
|
|
# local destination="$3"
|
|
# local -a matches=()
|
|
# if [[ ! -d "$artifact_dir" ]]; then
|
|
# echo "Expected artifact directory not found: $artifact_dir" >&2
|
|
# exit 1
|
|
# fi
|
|
# mapfile -d '' matches < <(find "$artifact_dir" -type f -name "$pattern" -print0 | sort -z)
|
|
# if [[ ${#matches[@]} -ne 1 ]]; then
|
|
# echo "Expected exactly one match for '$pattern' under '$artifact_dir', found ${#matches[@]}." >&2
|
|
# find "$artifact_dir" -type f | sort >&2
|
|
# exit 1
|
|
# fi
|
|
# cp -- "${matches[0]}" "$destination"
|
|
# }
|
|
# # Android
|
|
# copy_one "release-artifacts/${PREFIX}_signed-android" "*-Signed.apk" "release-upload/${PREFIX}_android-signed.apk"
|
|
# copy_one "release-artifacts/${PREFIX}_signed-android" "*-Signed.aab" "release-upload/${PREFIX}_android-signed.aab"
|
|
# # Windows msixbundle (signed)
|
|
# copy_one "release-artifacts/${PREFIX}_signed-windows.msixbundle" "*.msixbundle" "release-upload/${PREFIX}_windows.msixbundle"
|
|
# # Windows Store
|
|
# copy_one "release-artifacts/${PREFIX}_storeupload-windows" "*.msixupload" "release-upload/${PREFIX}_msstore.msixupload"
|
|
# # MacCatalyst
|
|
# copy_one "release-artifacts/${PREFIX}_signed-maccatalyst" "*.pkg" "release-upload/${PREFIX}_mac.pkg"
|
|
# # iOS
|
|
# copy_one "release-artifacts/${PREFIX}_ios-adhoc" "*.ipa" "release-upload/${PREFIX}_ios-adhoc.ipa"
|
|
# copy_one "release-artifacts/${PREFIX}_ios-store" "*.ipa" "release-upload/${PREFIX}_ios-store.ipa"
|
|
# echo "Files prepared for release:"
|
|
# ls -lh release-upload/
|
|
|
|
# - name: Create GitHub Release
|
|
# uses: softprops/action-gh-release@v3.0.0
|
|
# with:
|
|
# tag_name: "v${{needs.shared-resources.outputs.app_version}}"
|
|
# name: "${{env.APP_NAME}} v${{needs.shared-resources.outputs.app_version}}"
|
|
# draft: false
|
|
# prerelease: false
|
|
# generate_release_notes: true
|
|
# files: release-upload/* |