chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,896 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version (required, e.g. 2026.1.0)'
|
||||
required: true
|
||||
draft-release:
|
||||
description: 'Create the GitHub Release as a draft'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
skip-publish:
|
||||
description: 'Skip publishing to GitHub Releases'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
dry-run:
|
||||
description: 'Dry run (simulate without publishing)'
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
preflight:
|
||||
name: Preflight
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
package-env: ${{ steps.info.outputs.package-env }}
|
||||
package-version: ${{ steps.info.outputs.package-version }}
|
||||
onedrive-version: ${{ steps.info.outputs.onedrive-version }}
|
||||
draft-release: ${{ steps.info.outputs.draft-release }}
|
||||
skip-publish: ${{ steps.info.outputs.skip-publish }}
|
||||
dry-run: ${{ steps.info.outputs.dry-run }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Resolve build parameters
|
||||
id: info
|
||||
shell: pwsh
|
||||
run: |
|
||||
$IsProductionBranch = @('main', 'master') -contains '${{ github.ref_name }}'
|
||||
|
||||
try { $DraftRelease = [System.Boolean]::Parse('${{ inputs.draft-release }}') } catch { $DraftRelease = $false }
|
||||
try { $SkipPublish = [System.Boolean]::Parse('${{ inputs.skip-publish }}') } catch { $SkipPublish = $false }
|
||||
try { $DryRun = [System.Boolean]::Parse('${{ inputs.dry-run }}') } catch { $DryRun = $true }
|
||||
|
||||
$PackageEnv = if ($IsProductionBranch) {
|
||||
"publish-prod"
|
||||
} else {
|
||||
"publish-test"
|
||||
}
|
||||
|
||||
if (-Not $IsProductionBranch) {
|
||||
$DryRun = $true # force dry run when not on main/master branch
|
||||
}
|
||||
|
||||
if (-Not $SkipPublish -And $PackageEnv -ne 'publish-prod') {
|
||||
$DryRun = $true # force dry run when publishing outside production environment
|
||||
}
|
||||
|
||||
$PackageVersion = '${{ inputs.version }}'
|
||||
if ([string]::IsNullOrWhiteSpace($PackageVersion)) {
|
||||
throw "The workflow_dispatch version input is required."
|
||||
}
|
||||
|
||||
echo "package-env=$PackageEnv" >> $Env:GITHUB_OUTPUT
|
||||
$OneDriveVersion = "$PackageVersion.0"
|
||||
|
||||
echo "package-version=$PackageVersion" >> $Env:GITHUB_OUTPUT
|
||||
echo "onedrive-version=$OneDriveVersion" >> $Env:GITHUB_OUTPUT
|
||||
echo "draft-release=$($DraftRelease.ToString().ToLower())" >> $Env:GITHUB_OUTPUT
|
||||
echo "skip-publish=$($SkipPublish.ToString().ToLower())" >> $Env:GITHUB_OUTPUT
|
||||
echo "dry-run=$($DryRun.ToString().ToLower())" >> $Env:GITHUB_OUTPUT
|
||||
|
||||
echo "::notice::Environment: $PackageEnv"
|
||||
echo "::notice::Version: $PackageVersion"
|
||||
echo "::notice::DraftRelease: $DraftRelease"
|
||||
echo "::notice::DryRun: $DryRun"
|
||||
|
||||
build:
|
||||
name: Build & Sign (${{ matrix.platform }})
|
||||
runs-on: windows-latest
|
||||
needs: [preflight]
|
||||
environment: ${{ needs.preflight.outputs.package-env }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
UNIGETUI_GITHUB_CLIENT_ID: ${{ secrets.UNIGETUI_GITHUB_CLIENT_ID }}
|
||||
UNIGETUI_GITHUB_CLIENT_SECRET: ${{ secrets.UNIGETUI_GITHUB_CLIENT_SECRET }}
|
||||
UNIGETUI_OPENSEARCH_USERNAME: ${{ secrets.UNIGETUI_OPENSEARCH_USERNAME }}
|
||||
UNIGETUI_OPENSEARCH_PASSWORD: ${{ secrets.UNIGETUI_OPENSEARCH_PASSWORD }}
|
||||
NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [x64, arm64]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Validate GitHub OAuth secrets
|
||||
shell: pwsh
|
||||
run: |
|
||||
if ([string]::IsNullOrWhiteSpace($env:UNIGETUI_GITHUB_CLIENT_ID)) {
|
||||
throw "UNIGETUI_GITHUB_CLIENT_ID is not configured for this build environment."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:UNIGETUI_GITHUB_CLIENT_SECRET)) {
|
||||
throw "UNIGETUI_GITHUB_CLIENT_SECRET is not configured for this build environment."
|
||||
}
|
||||
|
||||
Write-Host "::notice::GitHub OAuth secrets are configured for this build."
|
||||
|
||||
- name: Install .NET
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.NUGET_PACKAGES }}
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install Inno Setup
|
||||
shell: pwsh
|
||||
run: |
|
||||
choco install innosetup -y --no-progress
|
||||
echo "C:\Program Files (x86)\Inno Setup 6" >> $Env:GITHUB_PATH
|
||||
|
||||
- name: Install code-signing tools
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet tool install --global AzureSignTool
|
||||
Install-Module -Name Devolutions.Authenticode -Force
|
||||
|
||||
# Trust test code-signing CA
|
||||
$TestCertsUrl = "https://raw.githubusercontent.com/Devolutions/devolutions-authenticode/master/data/certs"
|
||||
Invoke-WebRequest -Uri "$TestCertsUrl/authenticode-test-ca.crt" -OutFile ".\authenticode-test-ca.crt"
|
||||
Import-Certificate -FilePath ".\authenticode-test-ca.crt" -CertStoreLocation "cert:\LocalMachine\Root"
|
||||
Remove-Item ".\authenticode-test-ca.crt" -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
- name: Set version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
|
||||
.\scripts\set-version.ps1 -Version $PackageVersion
|
||||
|
||||
- name: Restore dependencies
|
||||
working-directory: src
|
||||
run: dotnet restore UniGetUI.Windows.slnx
|
||||
|
||||
- name: Run tests
|
||||
working-directory: src
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet build-server shutdown
|
||||
# Retry once to handle flaky tests (e.g. TaskRecyclerTests uses Random)
|
||||
dotnet test UniGetUI.Windows.slnx --no-restore --verbosity q --nologo /p:Platform=x64 /p:UseSharedCompilation=false /m:1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "::warning::First test run failed, retrying..."
|
||||
dotnet build-server shutdown
|
||||
dotnet test UniGetUI.Windows.slnx --no-restore --verbosity q --nologo /p:Platform=x64 /p:UseSharedCompilation=false /m:1
|
||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||
}
|
||||
|
||||
- name: Publish
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Platform = '${{ matrix.platform }}'
|
||||
$TargetFramework = (
|
||||
dotnet msbuild src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj `
|
||||
/nologo `
|
||||
-getProperty:TargetFramework `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform=$Platform `
|
||||
/p:RuntimeIdentifier=win-$Platform |
|
||||
Select-Object -Last 1
|
||||
).Trim()
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($TargetFramework)) {
|
||||
throw "Could not resolve the Avalonia target framework from MSBuild"
|
||||
}
|
||||
|
||||
dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj `
|
||||
/noLogo `
|
||||
/p:Configuration=Release `
|
||||
/p:Platform=$Platform `
|
||||
/p:RuntimeIdentifier=win-$Platform `
|
||||
/p:PublishProfile=Win-$Platform-NativeAot `
|
||||
/p:UseSharedCompilation=false `
|
||||
/p:BuildInParallel=false `
|
||||
/m:1 `
|
||||
-v m
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet publish Avalonia failed" }
|
||||
|
||||
# Stage binaries
|
||||
$PublishDir = "src/UniGetUI.Avalonia/bin/$Platform/Release/$TargetFramework/win-$Platform/publish"
|
||||
if (Test-Path "unigetui_bin") { Remove-Item "unigetui_bin" -Recurse -Force }
|
||||
New-Item "unigetui_bin" -ItemType Directory | Out-Null
|
||||
Get-ChildItem $PublishDir | Move-Item -Destination "unigetui_bin" -Force
|
||||
|
||||
if (-not (Test-Path "unigetui_bin/UniGetUI.exe")) {
|
||||
throw "Windows app host was not produced at unigetui_bin/UniGetUI.exe"
|
||||
}
|
||||
|
||||
$MaxShippedPdbSizeBytes = 1MB
|
||||
$PdbsToRemove = Get-ChildItem "unigetui_bin" -Filter "*.pdb" -File -Recurse | Where-Object {
|
||||
$_.Length -gt $MaxShippedPdbSizeBytes
|
||||
}
|
||||
|
||||
if ($PdbsToRemove.Count -gt 0) {
|
||||
$RemovedPdbBytes = ($PdbsToRemove | Measure-Object -Property Length -Sum).Sum
|
||||
$PdbsToRemove | Remove-Item -Force
|
||||
Write-Host ("Removed {0} oversized PDBs above {1:N2} MiB ({2:N2} MiB total)." -f $PdbsToRemove.Count, ($MaxShippedPdbSizeBytes / 1MB), ($RemovedPdbBytes / 1MB))
|
||||
}
|
||||
|
||||
- name: Code-sign binaries
|
||||
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ListPath = Join-Path $PWD "signing-files.txt"
|
||||
$files = Get-ChildItem "unigetui_bin" -Recurse -Include "*.exe", "*.dll" | Where-Object {
|
||||
(Get-AuthenticodeSignature $_.FullName).Status -eq "NotSigned"
|
||||
}
|
||||
$files.FullName | Set-Content $ListPath
|
||||
Write-Host "Signing list contains $($files.Count) files."
|
||||
|
||||
.\scripts\sign.ps1 `
|
||||
-FileListPath $ListPath `
|
||||
-AzureTenantId '${{ secrets.AZURE_TENANT_ID }}' `
|
||||
-KeyVaultUrl '${{ secrets.CODE_SIGNING_KEYVAULT_URL }}' `
|
||||
-ClientId '${{ secrets.CODE_SIGNING_CLIENT_ID }}' `
|
||||
-ClientSecret '${{ secrets.CODE_SIGNING_CLIENT_SECRET }}' `
|
||||
-CertificateName '${{ secrets.CODE_SIGNING_CERTIFICATE_NAME }}' `
|
||||
-TimestampServer '${{ vars.CODE_SIGNING_TIMESTAMP_SERVER }}'
|
||||
|
||||
- name: Build installer
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Platform = '${{ matrix.platform }}'
|
||||
$OutputDir = Join-Path $PWD "output"
|
||||
New-Item $OutputDir -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
.\scripts\refresh-integrity-tree.ps1 -Path $PWD/unigetui_bin -FailOnUnexpectedFiles
|
||||
|
||||
# Configure Inno Setup to use AzureSignTool
|
||||
$IssPath = "UniGetUI.iss"
|
||||
|
||||
# Build the installer (signing of the installer itself happens in the next step)
|
||||
# Temporarily remove SignTool line so ISCC doesn't try to sign during build
|
||||
$issContent = Get-Content $IssPath -Raw
|
||||
|
||||
try {
|
||||
$issContentNoSign = $issContent -Replace '(?m)^SignTool=.*$', '; SignTool=azsign (disabled for CI, signed separately)'
|
||||
$issContentNoSign = $issContentNoSign -Replace '(?m)^SignedUninstaller=yes', 'SignedUninstaller=no'
|
||||
Set-Content $IssPath $issContentNoSign -NoNewline
|
||||
|
||||
$InstallerBaseName = "UniGetUI.Installer.$Platform"
|
||||
$IsccArgs = @($IssPath, "/F$InstallerBaseName", "/O$OutputDir")
|
||||
if ([System.Boolean]::Parse('${{ needs.preflight.outputs.dry-run }}') -eq $false) {
|
||||
Write-Host "Using lzma/ultra64 installer compression for release build."
|
||||
$IsccArgs = @('/DInstallerCompression=lzma/ultra64') + $IsccArgs
|
||||
}
|
||||
|
||||
& ISCC.exe @IsccArgs
|
||||
if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed with exit code $LASTEXITCODE" }
|
||||
}
|
||||
finally {
|
||||
Set-Content $IssPath $issContent -NoNewline
|
||||
}
|
||||
|
||||
- name: Stage output
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Platform = '${{ matrix.platform }}'
|
||||
New-Item "output" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
.\scripts\refresh-integrity-tree.ps1 -Path $PWD/unigetui_bin -FailOnUnexpectedFiles
|
||||
|
||||
# Zip
|
||||
Compress-Archive -Path "unigetui_bin/*" -DestinationPath "output/UniGetUI.$Platform.zip" -CompressionLevel Optimal
|
||||
|
||||
# Installer is created in output during the previous step
|
||||
|
||||
- name: Capture artifact sizes
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Platform = '${{ matrix.platform }}'
|
||||
$SizesFile = "output/sizes.$Platform.json"
|
||||
$UncompressedSize = (Get-ChildItem "unigetui_bin" -Recurse -File | Measure-Object -Property Length -Sum).Sum
|
||||
$Artifacts = Get-ChildItem "output" -File | Where-Object { $_.Name -ne "sizes.$Platform.json" } | ForEach-Object {
|
||||
@{ Name = $_.Name; SizeBytes = $_.Length }
|
||||
}
|
||||
$Report = @{
|
||||
Platform = $Platform
|
||||
RuntimeIdentifier = "win-$Platform"
|
||||
UncompressedSizeBytes = $UncompressedSize
|
||||
Artifacts = $Artifacts
|
||||
Timestamp = [DateTimeOffset]::UtcNow.ToString("o")
|
||||
}
|
||||
$Report | ConvertTo-Json -Depth 4 | Set-Content $SizesFile -Encoding UTF8
|
||||
Write-Host "Artifact sizes saved to $SizesFile"
|
||||
Get-Content $SizesFile
|
||||
|
||||
- name: Code-sign installer
|
||||
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Platform = '${{ matrix.platform }}'
|
||||
.\scripts\sign.ps1 `
|
||||
-InstallerPath "output/UniGetUI.Installer.$Platform.exe" `
|
||||
-AzureTenantId '${{ secrets.AZURE_TENANT_ID }}' `
|
||||
-KeyVaultUrl '${{ secrets.CODE_SIGNING_KEYVAULT_URL }}' `
|
||||
-ClientId '${{ secrets.CODE_SIGNING_CLIENT_ID }}' `
|
||||
-ClientSecret '${{ secrets.CODE_SIGNING_CLIENT_SECRET }}' `
|
||||
-CertificateName '${{ secrets.CODE_SIGNING_CERTIFICATE_NAME }}' `
|
||||
-TimestampServer '${{ vars.CODE_SIGNING_TIMESTAMP_SERVER }}'
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: UniGetUI-release-${{ matrix.platform }}
|
||||
path: output/*
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
shell: pwsh
|
||||
run: |
|
||||
Remove-Item "unigetui_bin" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
build-avalonia:
|
||||
name: Build (${{ matrix.name }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [preflight]
|
||||
environment: ${{ needs.preflight.outputs.package-env }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
UNIGETUI_GITHUB_CLIENT_ID: ${{ secrets.UNIGETUI_GITHUB_CLIENT_ID }}
|
||||
UNIGETUI_GITHUB_CLIENT_SECRET: ${{ secrets.UNIGETUI_GITHUB_CLIENT_SECRET }}
|
||||
UNIGETUI_OPENSEARCH_USERNAME: ${{ secrets.UNIGETUI_OPENSEARCH_USERNAME }}
|
||||
UNIGETUI_OPENSEARCH_PASSWORD: ${{ secrets.UNIGETUI_OPENSEARCH_PASSWORD }}
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
SIGNING_IDENTITY: 'Developer ID Application: Devolutions inc.'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
name: macos-arm64
|
||||
runtime: osx-arm64
|
||||
publish_profile: osx-arm64-NativeAot
|
||||
- os: macos-latest
|
||||
name: macos-x64
|
||||
runtime: osx-x64
|
||||
publish_profile: osx-x64-NativeAot
|
||||
- os: ubuntu-latest
|
||||
name: linux-x64
|
||||
runtime: linux-x64
|
||||
publish_profile: linux-x64-NativeAot
|
||||
- os: ubuntu-latest
|
||||
name: linux-arm64
|
||||
runtime: linux-arm64
|
||||
publish_profile: linux-arm64-NativeAot
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install .NET
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.NUGET_PACKAGES }}
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
- name: Install Linux arm64 NativeAOT toolchain
|
||||
if: matrix.runtime == 'linux-arm64'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
. /etc/os-release
|
||||
sudo dpkg --add-architecture arm64
|
||||
|
||||
# Keep the runner's default Ubuntu feeds bound to amd64, then add the
|
||||
# Ubuntu Ports arm64 feed that provides the target CRT and zlib objects.
|
||||
if compgen -G "/etc/apt/sources.list.d/*.sources" > /dev/null; then
|
||||
for source in /etc/apt/sources.list.d/*.sources; do
|
||||
if ! grep -q '^Architectures:' "$source"; then
|
||||
sudo sed -i '/^Types: deb$/a Architectures: amd64' "$source"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -f /etc/apt/sources.list ]; then
|
||||
sudo sed -i -E '/^deb /s/^deb /deb [arch=amd64] /' /etc/apt/sources.list
|
||||
fi
|
||||
|
||||
sudo tee /etc/apt/sources.list.d/ubuntu-arm64.sources >/dev/null <<EOF
|
||||
Types: deb
|
||||
URIs: http://ports.ubuntu.com/ubuntu-ports/
|
||||
Suites: ${VERSION_CODENAME} ${VERSION_CODENAME}-updates ${VERSION_CODENAME}-backports
|
||||
Components: main restricted universe multiverse
|
||||
Architectures: arm64
|
||||
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
|
||||
|
||||
Types: deb
|
||||
URIs: http://ports.ubuntu.com/ubuntu-ports/
|
||||
Suites: ${VERSION_CODENAME}-security
|
||||
Components: main restricted universe multiverse
|
||||
Architectures: arm64
|
||||
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
|
||||
EOF
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
clang \
|
||||
llvm \
|
||||
binutils-aarch64-linux-gnu \
|
||||
gcc-aarch64-linux-gnu \
|
||||
zlib1g-dev:arm64
|
||||
|
||||
- name: Set version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
|
||||
./scripts/set-version.ps1 -Version $PackageVersion
|
||||
|
||||
- name: Restore dependencies
|
||||
working-directory: src
|
||||
run: dotnet restore UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --runtime ${{ matrix.runtime }}
|
||||
|
||||
- name: Publish
|
||||
working-directory: src
|
||||
shell: pwsh
|
||||
run: |
|
||||
$PublishProfile = '${{ matrix.publish_profile }}'
|
||||
$PublishArgs = @(
|
||||
'publish',
|
||||
'UniGetUI.Avalonia/UniGetUI.Avalonia.csproj',
|
||||
'--configuration', 'Release',
|
||||
'--runtime', '${{ matrix.runtime }}',
|
||||
'--self-contained', 'true',
|
||||
'--output', '../bin/${{ matrix.name }}'
|
||||
)
|
||||
if ($PublishProfile) {
|
||||
$PublishArgs += "/p:PublishProfile=$PublishProfile"
|
||||
}
|
||||
dotnet @PublishArgs
|
||||
|
||||
- name: Remove oversized Linux debug symbols
|
||||
if: runner.os == 'Linux'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$PublishDir = "bin/${{ matrix.name }}"
|
||||
$MaxDebugSymbolSizeBytes = 1MB
|
||||
$DebugSymbolsToRemove = @(Get-ChildItem $PublishDir -Include "*.dbg", "*.pdb" -File -Recurse | Where-Object {
|
||||
$_.Length -gt $MaxDebugSymbolSizeBytes
|
||||
})
|
||||
|
||||
if ($DebugSymbolsToRemove.Count -gt 0) {
|
||||
$RemovedDebugSymbolBytes = ($DebugSymbolsToRemove | Measure-Object -Property Length -Sum).Sum
|
||||
$DebugSymbolsToRemove | Remove-Item -Force
|
||||
Write-Host ("Removed {0} oversized Linux debug symbols above {1:N2} MiB ({2:N2} MiB total)." -f $DebugSymbolsToRemove.Count, ($MaxDebugSymbolSizeBytes / 1MB), ($RemovedDebugSymbolBytes / 1MB))
|
||||
}
|
||||
|
||||
- name: Install macOS packaging tools
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install create-dmg
|
||||
|
||||
- name: Check out Devolutions/actions
|
||||
if: ${{ runner.os == 'macOS' && fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: Devolutions/actions
|
||||
ref: v1
|
||||
token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
|
||||
path: ./.github/workflows
|
||||
|
||||
- name: Import Developer ID certificate
|
||||
if: ${{ runner.os == 'macOS' && fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
uses: ./.github/workflows/macos-code-sign-setup
|
||||
with:
|
||||
base64_certificate: ${{ secrets.APPLE_APP_DEV_ID_APP_CERTIFICATE }}
|
||||
certificate_password: ${{ secrets.APPLE_APP_DEV_ID_APP_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Package (macOS)
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION='${{ needs.preflight.outputs.package-version }}'
|
||||
APP_BUNDLE="UniGetUI.app"
|
||||
APP_EXECUTABLE="UniGetUI"
|
||||
DRY_RUN='${{ needs.preflight.outputs.dry-run }}'
|
||||
|
||||
rm -rf "${APP_BUNDLE}" output
|
||||
mkdir -p output
|
||||
mkdir -p "${APP_BUNDLE}/Contents/MacOS"
|
||||
mkdir -p "${APP_BUNDLE}/Contents/Resources"
|
||||
|
||||
cp -R "bin/${{ matrix.name }}/." "${APP_BUNDLE}/Contents/MacOS/"
|
||||
find "${APP_BUNDLE}/Contents/MacOS" -name "*.pdb" -type f -delete
|
||||
chmod +x "${APP_BUNDLE}/Contents/MacOS/${APP_EXECUTABLE}"
|
||||
sed "s|@VERSION@|${VERSION}|g" \
|
||||
scripts/macos/Info.plist \
|
||||
> "${APP_BUNDLE}/Contents/Info.plist"
|
||||
# Compile the appearance-aware app icon (Default/Dark/Tinted/Clear) from the Icon Composer
|
||||
# .icon into the bundle: produces Assets.car + AppIcon.icns. Info.plist already declares
|
||||
# CFBundleIconName=AppIcon, so macOS renders the appearance styles itself (incl. when closed).
|
||||
xcrun actool scripts/macos/AppIcon.icon \
|
||||
--compile "${APP_BUNDLE}/Contents/Resources" \
|
||||
--app-icon AppIcon \
|
||||
--output-partial-info-plist "$(mktemp).plist" \
|
||||
--platform macosx \
|
||||
--minimum-deployment-target 12.0 \
|
||||
--errors --warnings
|
||||
# Static .icns fallback for macOS < 26 (CFBundleIconFile) and the dmg volume icon.
|
||||
cp scripts/macos/UniGetUI.icns "${APP_BUNDLE}/Contents/Resources/"
|
||||
|
||||
if [ "$DRY_RUN" = "false" ]; then
|
||||
ENTITLEMENTS="scripts/macos/Entitlements.plist"
|
||||
find "${APP_BUNDLE}/Contents/MacOS" -type f \! -name "${APP_EXECUTABLE}" -print0 \
|
||||
| while IFS= read -r -d '' f; do
|
||||
codesign --force --timestamp --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$SIGNING_IDENTITY" "$f"
|
||||
done
|
||||
codesign --force --timestamp --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$SIGNING_IDENTITY" "${APP_BUNDLE}"
|
||||
codesign --verify --verbose=2 "${APP_BUNDLE}"
|
||||
else
|
||||
find "${APP_BUNDLE}/Contents/MacOS" -type f | while IFS= read -r f; do
|
||||
codesign --force --sign - "$f" 2>/dev/null || true
|
||||
done
|
||||
codesign --force --sign - "${APP_BUNDLE}"
|
||||
fi
|
||||
|
||||
COPYFILE_DISABLE=1 tar -czf "output/UniGetUI.${{ matrix.name }}.tar.gz" "${APP_BUNDLE}"
|
||||
|
||||
create-dmg \
|
||||
--volname "UniGetUI" \
|
||||
--volicon "scripts/macos/UniGetUI.icns" \
|
||||
--window-pos 200 120 \
|
||||
--window-size 600 400 \
|
||||
--icon-size 128 \
|
||||
--icon "UniGetUI.app" 150 185 \
|
||||
--hide-extension "UniGetUI.app" \
|
||||
--app-drop-link 450 185 \
|
||||
"output/UniGetUI.${{ matrix.name }}.dmg" \
|
||||
"${APP_BUNDLE}"
|
||||
|
||||
test -f "output/UniGetUI.${{ matrix.name }}.tar.gz"
|
||||
test -f "output/UniGetUI.${{ matrix.name }}.dmg"
|
||||
|
||||
- name: Sign DMG
|
||||
if: ${{ runner.os == 'macOS' && fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
DMG="output/UniGetUI.${{ matrix.name }}.dmg"
|
||||
codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$DMG"
|
||||
codesign --verify --verbose=2 "$DMG"
|
||||
|
||||
- name: Notarize DMG
|
||||
if: ${{ runner.os == 'macOS' && fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
uses: ./.github/workflows/macos-notarize
|
||||
timeout-minutes: 15
|
||||
with:
|
||||
apple_password: ${{ secrets.APPLE_BOT_PASSWORD }}
|
||||
package_path: output/UniGetUI.${{ matrix.name }}.dmg
|
||||
|
||||
- name: Install Linux packaging tools
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y rpm
|
||||
|
||||
- name: Package (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Version = '${{ needs.preflight.outputs.package-version }}'
|
||||
$Parts = $Version -split '-', 2
|
||||
$BaseVersion = $Parts[0]
|
||||
$Prerelease = if ($Parts.Count -gt 1) { $Parts[1] } else { $null }
|
||||
|
||||
# deb: use tilde notation for pre-releases (Ubuntu 18.04+ compatible)
|
||||
$DebVersion = if ($Prerelease) { "${BaseVersion}~${Prerelease}" } else { $Version }
|
||||
# rpm: pre-release encoded in Release/Iteration field (RHEL 8+ / RPM 4.14+)
|
||||
$RpmVersion = $BaseVersion
|
||||
$RpmIteration = if ($Prerelease) { "0.${Prerelease}" } else { '1' }
|
||||
|
||||
# Map .NET RID -> package arch names
|
||||
$DebArch, $RpmArch = switch ('${{ matrix.runtime }}') {
|
||||
'linux-arm64' { 'arm64', 'aarch64' }
|
||||
default { 'amd64', 'x86_64' }
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path output | Out-Null
|
||||
|
||||
# .tar.gz
|
||||
& tar -czf "output/UniGetUI.${{ matrix.name }}.tar.gz" -C "bin/${{ matrix.name }}" .
|
||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||
|
||||
# .deb — Ubuntu 18.04+ (glibc 2.27, Debian policy 3.9.6)
|
||||
& ./scripts/package-linux.ps1 `
|
||||
-PackageType deb `
|
||||
-SourceDir "bin/${{ matrix.name }}" `
|
||||
-OutputPath "output/UniGetUI.${{ matrix.name }}.deb" `
|
||||
-Version $DebVersion `
|
||||
-Architecture $DebArch
|
||||
|
||||
# .rpm — RHEL 8+ (RPM 4.14, glibc 2.28)
|
||||
& ./scripts/package-linux.ps1 `
|
||||
-PackageType rpm `
|
||||
-SourceDir "bin/${{ matrix.name }}" `
|
||||
-OutputPath "output/UniGetUI.${{ matrix.name }}.rpm" `
|
||||
-Version $RpmVersion `
|
||||
-Iteration $RpmIteration `
|
||||
-Architecture $RpmArch
|
||||
|
||||
- name: Capture artifact sizes
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Name = '${{ matrix.name }}'
|
||||
$Runtime = '${{ matrix.runtime }}'
|
||||
$SizesFile = "output/sizes.$Name.json"
|
||||
$SourceDir = if ('${{ runner.os }}' -eq 'macOS') { "UniGetUI.app/Contents/MacOS" } else { "bin/$Name" }
|
||||
$UncompressedSize = (Get-ChildItem $SourceDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
|
||||
$Artifacts = Get-ChildItem "output" -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne "sizes.$Name.json" } | ForEach-Object {
|
||||
@{ Name = $_.Name; SizeBytes = $_.Length }
|
||||
}
|
||||
$Report = @{
|
||||
Platform = $Name
|
||||
RuntimeIdentifier = $Runtime
|
||||
UncompressedSizeBytes = $UncompressedSize
|
||||
Artifacts = $Artifacts
|
||||
Timestamp = [DateTimeOffset]::UtcNow.ToString("o")
|
||||
}
|
||||
$Report | ConvertTo-Json -Depth 4 | Set-Content $SizesFile -Encoding UTF8
|
||||
Write-Host "Artifact sizes saved to $SizesFile"
|
||||
Get-Content $SizesFile
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: UniGetUI-${{ matrix.name }}
|
||||
path: output/*
|
||||
|
||||
publish:
|
||||
name: Publish GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [preflight, build, build-avalonia]
|
||||
if: ${{ fromJSON(needs.preflight.outputs.skip-publish) == false }}
|
||||
environment: ${{ needs.preflight.outputs.package-env }}
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: output
|
||||
|
||||
- name: Add legacy installer filename
|
||||
shell: pwsh
|
||||
working-directory: output
|
||||
run: |
|
||||
$InstallerFiles = Get-ChildItem -Path . -Recurse -File -Filter "UniGetUI.Installer.x64.exe"
|
||||
if (-not $InstallerFiles) {
|
||||
throw "Could not find UniGetUI.Installer.x64.exe in downloaded artifacts"
|
||||
}
|
||||
|
||||
$InstallerFiles | ForEach-Object {
|
||||
$LegacyInstallerPath = Join-Path $_.DirectoryName "UniGetUI.Installer.exe"
|
||||
Copy-Item -Path $_.FullName -Destination $LegacyInstallerPath -Force
|
||||
Write-Host "Created legacy installer alias: $LegacyInstallerPath"
|
||||
}
|
||||
|
||||
- name: Generate consolidated checksums
|
||||
shell: pwsh
|
||||
working-directory: output
|
||||
run: |
|
||||
$ChecksumFile = Join-Path $PWD "checksums.txt"
|
||||
$ChecksumLines = Get-ChildItem -Path . -Recurse -File | Where-Object {
|
||||
$_.Name -notmatch '^checksums(\..+)?\.txt$'
|
||||
} | Sort-Object Name | ForEach-Object {
|
||||
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
|
||||
"$hash $($_.Name)"
|
||||
}
|
||||
|
||||
Set-Content -Path $ChecksumFile -Value $ChecksumLines -Encoding utf8NoBOM
|
||||
|
||||
echo "::group::checksums"
|
||||
Get-Content $ChecksumFile
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Generate consolidated size report
|
||||
shell: pwsh
|
||||
working-directory: output
|
||||
run: |
|
||||
$SizeFiles = Get-ChildItem -Path . -Recurse -File -Filter "sizes.*.json" | Sort-Object Name
|
||||
if (-not $SizeFiles) {
|
||||
Write-Host "No size files found."
|
||||
return
|
||||
}
|
||||
|
||||
$Rows = foreach ($file in $SizeFiles) {
|
||||
$data = Get-Content $file.FullName -Raw | ConvertFrom-Json
|
||||
$uncompressedMiB = [math]::Round($data.UncompressedSizeBytes / 1MB, 2)
|
||||
foreach ($artifact in $data.Artifacts) {
|
||||
[PSCustomObject]@{
|
||||
Platform = $data.Platform
|
||||
RID = $data.RuntimeIdentifier
|
||||
Artifact = $artifact.Name
|
||||
CompressedMiB = [math]::Round($artifact.SizeBytes / 1MB, 2)
|
||||
UncompressedMiB = $uncompressedMiB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$SizeReportFile = Join-Path $PWD "sizes.md"
|
||||
$Lines = @("# UniGetUI Release Artifact Sizes", "")
|
||||
$Lines += $Rows | Format-Table -AutoSize | Out-String -Stream | Where-Object { $_.Trim() -ne '' }
|
||||
Set-Content -Path $SizeReportFile -Value $Lines -Encoding utf8NoBOM
|
||||
|
||||
echo "::group::sizes"
|
||||
Get-Content $SizeReportFile
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Create GitHub Release
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
working-directory: output
|
||||
run: |
|
||||
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
|
||||
$DraftRelease = [System.Boolean]::Parse('${{ needs.preflight.outputs.draft-release }}')
|
||||
$DryRun = [System.Boolean]::Parse('${{ needs.preflight.outputs.dry-run }}')
|
||||
|
||||
echo "::group::checksums"
|
||||
Get-Content "./checksums.txt"
|
||||
echo "::endgroup::"
|
||||
|
||||
$ReleaseTag = "v$PackageVersion"
|
||||
$ReleaseTitle = "UniGetUI v${PackageVersion}"
|
||||
$Repository = $Env:GITHUB_REPOSITORY
|
||||
$DraftArg = if ($DraftRelease) { '--draft' } else { $null }
|
||||
|
||||
$Files = Get-ChildItem -Path . -Recurse -File | Where-Object {
|
||||
$_.Name -eq 'checksums.txt' -or $_.Name -notmatch '^checksums\..+\.txt$'
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host "Dry Run: skipping GitHub release creation!"
|
||||
Write-Host "Would create release $ReleaseTag with title '$ReleaseTitle' (draft=$DraftRelease)"
|
||||
$Files | ForEach-Object { Write-Host " - $($_.FullName)" }
|
||||
} else {
|
||||
if ($DraftArg) {
|
||||
& gh release create $ReleaseTag --repo $Repository --title $ReleaseTitle $DraftArg $Files.FullName
|
||||
} else {
|
||||
& gh release create $ReleaseTag --repo $Repository --title $ReleaseTitle $Files.FullName
|
||||
}
|
||||
}
|
||||
|
||||
- name: Check out Devolutions/actions
|
||||
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: Devolutions/actions
|
||||
ref: v1
|
||||
token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
|
||||
path: ./.github/workflows
|
||||
|
||||
- name: Install Devolutions Toolbox
|
||||
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
uses: ./.github/workflows/toolbox-install
|
||||
with:
|
||||
github_token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
|
||||
|
||||
- name: Stage files for OneDrive upload
|
||||
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
|
||||
New-Item -Path "onedrive-staging" -ItemType Directory -Force | Out-Null
|
||||
|
||||
$OneDriveVersion = '${{ needs.preflight.outputs.onedrive-version }}'
|
||||
|
||||
$Mappings = @{
|
||||
"output/UniGetUI-release-x64/UniGetUI.Installer.x64.exe" = "Devolutions.UniGetUI.win-x64.$OneDriveVersion.exe"
|
||||
"output/UniGetUI-release-arm64/UniGetUI.Installer.arm64.exe" = "Devolutions.UniGetUI.win-arm64.$OneDriveVersion.exe"
|
||||
"output/UniGetUI-release-x64/UniGetUI.x64.zip" = "Devolutions.UniGetUI.win-x64.$OneDriveVersion.zip"
|
||||
"output/UniGetUI-release-arm64/UniGetUI.arm64.zip" = "Devolutions.UniGetUI.win-arm64.$OneDriveVersion.zip"
|
||||
"output/UniGetUI-macos-x64/UniGetUI.macos-x64.dmg" = "Devolutions.UniGetUI.macos-x64.$OneDriveVersion.dmg"
|
||||
"output/UniGetUI-macos-arm64/UniGetUI.macos-arm64.dmg" = "Devolutions.UniGetUI.macos-arm64.$OneDriveVersion.dmg"
|
||||
"output/UniGetUI-macos-x64/UniGetUI.macos-x64.tar.gz" = "Devolutions.UniGetUI.macos-x64.$OneDriveVersion.tar.gz"
|
||||
"output/UniGetUI-macos-arm64/UniGetUI.macos-arm64.tar.gz" = "Devolutions.UniGetUI.macos-arm64.$OneDriveVersion.tar.gz"
|
||||
"output/UniGetUI-linux-x64/UniGetUI.linux-x64.deb" = "Devolutions.UniGetUI.linux-x64.$OneDriveVersion.deb"
|
||||
"output/UniGetUI-linux-arm64/UniGetUI.linux-arm64.deb" = "Devolutions.UniGetUI.linux-arm64.$OneDriveVersion.deb"
|
||||
"output/UniGetUI-linux-x64/UniGetUI.linux-x64.rpm" = "Devolutions.UniGetUI.linux-x64.$OneDriveVersion.rpm"
|
||||
"output/UniGetUI-linux-arm64/UniGetUI.linux-arm64.rpm" = "Devolutions.UniGetUI.linux-arm64.$OneDriveVersion.rpm"
|
||||
"output/UniGetUI-linux-x64/UniGetUI.linux-x64.tar.gz" = "Devolutions.UniGetUI.linux-x64.$OneDriveVersion.tar.gz"
|
||||
"output/UniGetUI-linux-arm64/UniGetUI.linux-arm64.tar.gz" = "Devolutions.UniGetUI.linux-arm64.$OneDriveVersion.tar.gz"
|
||||
}
|
||||
|
||||
foreach ($entry in $Mappings.GetEnumerator()) {
|
||||
if (-not (Test-Path $entry.Key)) {
|
||||
throw "File not found: $($entry.Key)"
|
||||
}
|
||||
Copy-Item -Path $entry.Key -Destination "onedrive-staging/$($entry.Value)"
|
||||
Write-Host "Staged: $($entry.Key) -> $($entry.Value)"
|
||||
}
|
||||
|
||||
- name: Upload to OneDrive
|
||||
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
|
||||
uses: ./.github/workflows/onedrive-upload
|
||||
with:
|
||||
azure_client_id: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_ID }}
|
||||
azure_client_secret: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_SECRET }}
|
||||
conflict_behavior: replace
|
||||
destination_path: /UniGetUI/${{ needs.preflight.outputs.onedrive-version }}
|
||||
remote: releases
|
||||
source_path: Devolutions.UniGetUI.*
|
||||
working_directory: onedrive-staging
|
||||
|
||||
size-report:
|
||||
name: Consolidated Size Report
|
||||
runs-on: ubuntu-latest
|
||||
needs: [preflight, build, build-avalonia]
|
||||
if: always() && needs.build.result != 'cancelled' && needs.build-avalonia.result != 'cancelled'
|
||||
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: output
|
||||
|
||||
- name: Generate consolidated size report
|
||||
shell: pwsh
|
||||
working-directory: output
|
||||
run: |
|
||||
$SizeFiles = Get-ChildItem -Path . -Recurse -File -Filter "sizes.*.json" | Sort-Object Name
|
||||
if (-not $SizeFiles) {
|
||||
Write-Host "No size files found."
|
||||
return
|
||||
}
|
||||
|
||||
$Rows = foreach ($file in $SizeFiles) {
|
||||
$data = Get-Content $file.FullName -Raw | ConvertFrom-Json
|
||||
$uncompressedMiB = [math]::Round($data.UncompressedSizeBytes / 1MB, 2)
|
||||
foreach ($artifact in $data.Artifacts) {
|
||||
[PSCustomObject]@{
|
||||
Platform = $data.Platform
|
||||
RID = $data.RuntimeIdentifier
|
||||
Artifact = $artifact.Name
|
||||
CompressedMiB = [math]::Round($artifact.SizeBytes / 1MB, 2)
|
||||
UncompressedMiB = $uncompressedMiB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$SizeReportFile = Join-Path $PWD "sizes.md"
|
||||
$Lines = @("# UniGetUI Release Artifact Sizes", "")
|
||||
$Lines += $Rows | Format-Table -AutoSize | Out-String -Stream | Where-Object { $_.Trim() -ne '' }
|
||||
Set-Content -Path $SizeReportFile -Value $Lines -Encoding utf8NoBOM
|
||||
|
||||
echo "::group::sizes"
|
||||
Get-Content $SizeReportFile
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Upload size report
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: UniGetUI-size-report
|
||||
path: output/sizes.md
|
||||
@@ -0,0 +1,179 @@
|
||||
name: CLI Headless E2E
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'src/**/*.cs'
|
||||
- 'src/**/*.csproj'
|
||||
- 'src/**/*.props'
|
||||
- 'src/**/*.targets'
|
||||
- 'src/**/*.sln'
|
||||
- 'src/**/*.slnx'
|
||||
- 'testing/automation/**'
|
||||
- '.github/workflows/cli-headless-e2e.yml'
|
||||
- 'global.json'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cli-headless-e2e:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Windows (Avalonia)
|
||||
os: windows-latest
|
||||
solution: UniGetUI.Windows.slnx
|
||||
daemon_project: UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
|
||||
daemon_build_args: '-p:Platform=x64'
|
||||
manifest: testing/automation/cli-e2e.manifest.windows.json
|
||||
- name: Windows (NativeAOT)
|
||||
os: windows-latest
|
||||
solution: UniGetUI.Windows.slnx
|
||||
daemon_project: UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
|
||||
daemon_publish_profile: Win-x64-NativeAot
|
||||
daemon_build_args: '-p:Platform=x64'
|
||||
manifest: testing/automation/cli-e2e.manifest.windows.json
|
||||
- name: Linux (Avalonia)
|
||||
os: ubuntu-latest
|
||||
solution: UniGetUI.Avalonia.slnx
|
||||
daemon_project: UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
|
||||
daemon_build_args: ''
|
||||
manifest: testing/automation/cli-e2e.manifest.linux.json
|
||||
- name: Linux (NativeAOT)
|
||||
os: ubuntu-latest
|
||||
solution: UniGetUI.Avalonia.slnx
|
||||
daemon_project: UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
|
||||
daemon_publish_profile: linux-x64-NativeAot
|
||||
daemon_build_args: ''
|
||||
manifest: testing/automation/cli-e2e.manifest.linux-nativeaot.json
|
||||
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CONFIGURATION: Release
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.NUGET_PACKAGES }}
|
||||
key: ${{ runner.os }}-nuget-e2e-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-e2e-
|
||||
|
||||
- name: Restore solution
|
||||
working-directory: src
|
||||
shell: pwsh
|
||||
run: dotnet restore ${{ matrix.solution }}
|
||||
|
||||
- name: Build headless daemon
|
||||
working-directory: src
|
||||
shell: pwsh
|
||||
run: |
|
||||
$PublishProfile = '${{ matrix.daemon_publish_profile }}'
|
||||
$BuildArgs = '${{ matrix.daemon_build_args }}'
|
||||
|
||||
dotnet build-server shutdown
|
||||
|
||||
if ($PublishProfile) {
|
||||
$RuntimeIdentifier = $PublishProfile -replace '-NativeAot$', '' -replace '^Win-', 'win-' -replace '^linux-', 'linux-' -replace '^osx-', 'osx-'
|
||||
$OutputPath = Join-Path (Resolve-Path '..') "artifacts/e2e-nativeaot/$RuntimeIdentifier"
|
||||
New-Item -ItemType Directory -Force -Path $OutputPath | Out-Null
|
||||
|
||||
$args = @(
|
||||
'publish',
|
||||
'${{ matrix.daemon_project }}',
|
||||
'--no-restore',
|
||||
'--configuration',
|
||||
'${{ env.CONFIGURATION }}',
|
||||
'--runtime',
|
||||
$RuntimeIdentifier,
|
||||
'--self-contained',
|
||||
'true',
|
||||
'--output',
|
||||
$OutputPath,
|
||||
'--verbosity',
|
||||
'minimal',
|
||||
'/p:UseSharedCompilation=false',
|
||||
'/m:1',
|
||||
"/p:PublishProfile=$PublishProfile"
|
||||
)
|
||||
if ($BuildArgs) {
|
||||
$args += $BuildArgs
|
||||
}
|
||||
dotnet @args
|
||||
|
||||
$exeName = if ('${{ runner.os }}' -eq 'Windows') { 'UniGetUI.exe' } else { 'UniGetUI' }
|
||||
$daemonExe = Join-Path $OutputPath $exeName
|
||||
if (-not (Test-Path $daemonExe)) {
|
||||
throw "NativeAOT daemon executable was not produced at $daemonExe"
|
||||
}
|
||||
if ('${{ runner.os }}' -ne 'Windows') {
|
||||
chmod +x $daemonExe
|
||||
}
|
||||
echo "UNIGETUI_DAEMON_EXE=$daemonExe" >> $Env:GITHUB_ENV
|
||||
}
|
||||
else {
|
||||
$args = @(
|
||||
'build',
|
||||
'${{ matrix.daemon_project }}',
|
||||
'--no-restore',
|
||||
'--configuration',
|
||||
'${{ env.CONFIGURATION }}',
|
||||
'--verbosity',
|
||||
'minimal',
|
||||
'/p:UseSharedCompilation=false',
|
||||
'/m:1'
|
||||
)
|
||||
if ($BuildArgs) {
|
||||
$args += $BuildArgs
|
||||
}
|
||||
dotnet @args
|
||||
}
|
||||
|
||||
- name: Upgrade pip tooling
|
||||
shell: pwsh
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
|
||||
- name: Show package-manager inventory
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet --version
|
||||
python --version
|
||||
python -m pip --version
|
||||
npm --version
|
||||
|
||||
- name: Run headless CLI E2E
|
||||
shell: pwsh
|
||||
env:
|
||||
UNIGETUI_CLI_E2E_MANIFEST: ${{ matrix.manifest }}
|
||||
UNIGETUI_CLI_E2E_ARTIFACTS: ${{ github.workspace }}/artifacts/cli-headless-e2e/${{ runner.os }}
|
||||
run: ./testing/automation/run-cli-e2e.ps1
|
||||
|
||||
- name: Upload CLI E2E artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: cli-headless-e2e-${{ matrix.name }}
|
||||
path: artifacts/cli-headless-e2e/${{ runner.os }}
|
||||
if-no-files-found: warn
|
||||
@@ -0,0 +1,90 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: csharp
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -0,0 +1,115 @@
|
||||
name: .NET Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'global.json'
|
||||
- '**.cs'
|
||||
- '**.axaml'
|
||||
- '**.csproj'
|
||||
- '**.props'
|
||||
- '**.targets'
|
||||
- '**.sln'
|
||||
- '**.slnx'
|
||||
- 'src/.editorconfig'
|
||||
- '.github/workflows/dotnet-test.yml'
|
||||
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'global.json'
|
||||
- '**.cs'
|
||||
- '**.axaml'
|
||||
- '**.csproj'
|
||||
- '**.props'
|
||||
- '**.targets'
|
||||
- '**.sln'
|
||||
- '**.slnx'
|
||||
- 'src/.editorconfig'
|
||||
- '.github/workflows/dotnet-test.yml'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test-codebase:
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install .NET SDK
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.NUGET_PACKAGES }}
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: src
|
||||
run: dotnet restore UniGetUI.Windows.slnx
|
||||
|
||||
- name: Check whitespace formatting
|
||||
run: dotnet format whitespace src --folder --verify-no-changes --verbosity minimal
|
||||
|
||||
- name: Check code style formatting (Avalonia Windows solution)
|
||||
working-directory: src
|
||||
run: dotnet format style UniGetUI.Windows.slnx --no-restore --verify-no-changes --verbosity minimal
|
||||
|
||||
- name: Build Avalonia Windows solution
|
||||
working-directory: src
|
||||
run: |
|
||||
dotnet build-server shutdown
|
||||
dotnet build UniGetUI.Windows.slnx --no-restore --verbosity minimal /p:Platform=x64 /p:UseSharedCompilation=false /m:1
|
||||
|
||||
- name: Run Tests
|
||||
working-directory: src
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: dotnet test UniGetUI.Windows.slnx --no-restore --verbosity q --nologo /p:Platform=x64 /p:UseSharedCompilation=false /m:1
|
||||
|
||||
- name: Report full-trim publish warnings
|
||||
working-directory: src
|
||||
shell: pwsh
|
||||
continue-on-error: true
|
||||
run: |
|
||||
dotnet build-server shutdown
|
||||
dotnet publish UniGetUI.Avalonia/UniGetUI.Avalonia.csproj `
|
||||
--no-restore `
|
||||
--configuration Release `
|
||||
--runtime win-x64 `
|
||||
--self-contained true `
|
||||
--output "${{ runner.temp }}\unigetui-full-trim-publish" `
|
||||
/p:Platform=x64 `
|
||||
/p:TrimMode=full `
|
||||
/p:TrimmerSingleWarn=false `
|
||||
/p:UseSharedCompilation=false `
|
||||
--verbosity minimal
|
||||
|
||||
- name: Report NativeAOT publish warnings
|
||||
working-directory: src
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet build-server shutdown
|
||||
dotnet publish UniGetUI.Avalonia/UniGetUI.Avalonia.csproj `
|
||||
--no-restore `
|
||||
--configuration Release `
|
||||
--runtime win-x64 `
|
||||
--self-contained true `
|
||||
--output "${{ runner.temp }}\unigetui-nativeaot-publish" `
|
||||
/m:1 `
|
||||
/p:Platform=x64 `
|
||||
/p:PublishProfile=Win-x64-NativeAot `
|
||||
/p:TrimmerSingleWarn=false `
|
||||
/p:UseSharedCompilation=false `
|
||||
--verbosity minimal
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Translation Validation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'scripts/translation/**'
|
||||
- '.agents/skills/translation-*/scripts/**'
|
||||
- 'src/Languages/**'
|
||||
- 'src/Languages/Data/LanguagesReference.json'
|
||||
- 'src/Languages/Data/TranslatedPercentages.json'
|
||||
- 'src/Languages/Data/Translators.json'
|
||||
- 'src/UniGetUI.Core.Data/Assets/Data/Contributors.list'
|
||||
- '.github/workflows/translation-validation.yml'
|
||||
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
- 'scripts/translation/**'
|
||||
- '.agents/skills/translation-*/scripts/**'
|
||||
- 'src/Languages/**'
|
||||
- 'src/Languages/Data/LanguagesReference.json'
|
||||
- 'src/Languages/Data/TranslatedPercentages.json'
|
||||
- 'src/Languages/Data/Translators.json'
|
||||
- 'src/UniGetUI.Core.Data/Assets/Data/Contributors.list'
|
||||
- '.github/workflows/translation-validation.yml'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate-translations:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Validate translation file structure
|
||||
shell: pwsh
|
||||
run: pwsh ./scripts/translation/Verify-Translations.ps1
|
||||
|
||||
- name: Validate boundary alignment
|
||||
shell: pwsh
|
||||
run: pwsh ./scripts/translation/Set-TranslationBoundaryOrder.ps1 -CheckOnly
|
||||
Reference in New Issue
Block a user