mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-24 18:44:38 +08:00
- Upgrade actions/checkout from v4 to v7 - Upgrade actions/setup-dotnet from v4 to v5 - Upgrade actions/upload-artifact from v4 to v7 - Upgrade actions/download-artifact from v4 to v4 (already compatible) - Upgrade ncipollo/release-action from v1 to v1.21.0 This addresses GitHub's deprecation of Node.js 20 and the upcoming forced migration to Node.js 24 (effective June 2, 2026).
898 lines
34 KiB
YAML
898 lines
34 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Release tag'
|
|
required: true
|
|
type: string
|
|
is_prerelease:
|
|
description: 'Pre-release'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
|
|
env:
|
|
DOTNET_VERSION: '10.0.x'
|
|
Solution_Name: LanMountainDesktop.slnx
|
|
DOTNET_gcServer: 1
|
|
|
|
jobs:
|
|
prepare:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.version.outputs.version }}
|
|
assembly_version: ${{ steps.version.outputs.assembly_version }}
|
|
informational_version: ${{ steps.version.outputs.informational_version }}
|
|
tag: ${{ steps.version.outputs.tag }}
|
|
checkout_ref: ${{ steps.version.outputs.checkout_ref }}
|
|
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
|
|
release_channel: ${{ steps.version.outputs.release_channel }}
|
|
|
|
steps:
|
|
- name: Checkout repository metadata
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Get release info
|
|
id: version
|
|
run: |
|
|
if [[ "${{ github.event_name }}" == "push" ]]; then
|
|
TAG="${GITHUB_REF#refs/tags/}"
|
|
CHECKOUT_REF="${GITHUB_REF}"
|
|
IS_PRERELEASE="false"
|
|
else
|
|
RAW_TAG="${{ github.event.inputs.tag }}"
|
|
if [[ "${RAW_TAG}" == refs/tags/* ]]; then
|
|
TAG="${RAW_TAG#refs/tags/}"
|
|
elif [[ "${RAW_TAG}" == v* ]]; then
|
|
TAG="${RAW_TAG}"
|
|
else
|
|
TAG="v${RAW_TAG}"
|
|
fi
|
|
|
|
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
|
CHECKOUT_REF="refs/tags/${TAG}"
|
|
else
|
|
CHECKOUT_REF="${GITHUB_SHA}"
|
|
fi
|
|
|
|
if [[ "${{ github.event.inputs.is_prerelease }}" == "true" ]]; then
|
|
IS_PRERELEASE="true"
|
|
else
|
|
IS_PRERELEASE="false"
|
|
fi
|
|
fi
|
|
|
|
VERSION="${TAG#v}"
|
|
IFS='.' read -r -a VERSION_PARTS <<< "${VERSION}"
|
|
while [ "${#VERSION_PARTS[@]}" -lt 4 ]; do
|
|
VERSION_PARTS+=("0")
|
|
done
|
|
ASSEMBLY_VERSION="${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}.${VERSION_PARTS[3]}"
|
|
|
|
if [[ "${IS_PRERELEASE}" == "true" ]]; then
|
|
RELEASE_CHANNEL="preview"
|
|
else
|
|
RELEASE_CHANNEL="stable"
|
|
fi
|
|
|
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "assembly_version=${ASSEMBLY_VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "informational_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "checkout_ref=${CHECKOUT_REF}" >> "$GITHUB_OUTPUT"
|
|
echo "is_prerelease=${IS_PRERELEASE}" >> "$GITHUB_OUTPUT"
|
|
echo "release_channel=${RELEASE_CHANNEL}" >> "$GITHUB_OUTPUT"
|
|
|
|
build-windows:
|
|
needs: prepare
|
|
runs-on: windows-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: x64
|
|
suffix: ''
|
|
- arch: x86
|
|
suffix: ''
|
|
name: Build_Windows_${{ matrix.arch }}${{ matrix.suffix }}
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
submodules: recursive
|
|
ref: ${{ needs.prepare.outputs.checkout_ref }}
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
dotnet-quality: 'preview'
|
|
|
|
- name: Stamp release version metadata
|
|
shell: pwsh
|
|
run: |
|
|
./scripts/Set-ReleaseVersion.ps1 `
|
|
-Version "${{ needs.prepare.outputs.version }}" `
|
|
-AssemblyVersion "${{ needs.prepare.outputs.assembly_version }}"
|
|
|
|
- name: Restore
|
|
run: dotnet restore ${{ env.Solution_Name }}
|
|
|
|
- name: Build
|
|
run: >
|
|
dotnet build ${{ env.Solution_Name }} -c Release --no-restore -v minimal
|
|
-p:Version=${{ needs.prepare.outputs.version }}
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }}
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }}
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Publish Launcher (AOT)
|
|
run: |
|
|
$version = "${{ needs.prepare.outputs.version }}"
|
|
$arch = "${{ matrix.arch }}"
|
|
$launcherPublishDir = "publish/launcher-win-$arch"
|
|
|
|
dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj `
|
|
-c Release `
|
|
-o ./$launcherPublishDir `
|
|
--self-contained `
|
|
-r win-$arch `
|
|
-p:PublishAot=true `
|
|
-p:PublishSingleFile=true `
|
|
-p:IncludeNativeLibrariesForSelfExtract=true `
|
|
-p:EnableCompressionInSingleFile=true `
|
|
-p:DebugType=none `
|
|
-p:DebugSymbols=false `
|
|
-p:Version=${{ needs.prepare.outputs.version }} `
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} `
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} `
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Launcher AOT publish failed"
|
|
exit 1
|
|
}
|
|
shell: pwsh
|
|
|
|
- name: Publish Main App
|
|
run: |
|
|
$publishDir = "publish/windows-${{ matrix.arch }}"
|
|
|
|
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj `
|
|
-c Release `
|
|
-o ./$publishDir `
|
|
--self-contained:false `
|
|
-r win-${{ matrix.arch }} `
|
|
-p:SelfContained=false `
|
|
-p:PublishSingleFile=false `
|
|
-p:DebugType=none `
|
|
-p:DebugSymbols=false `
|
|
-p:SkipAirAppHostBuild=true `
|
|
-p:PublishTrimmed=false `
|
|
-p:PublishReadyToRun=false `
|
|
-p:Version=${{ needs.prepare.outputs.version }} `
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} `
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} `
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
shell: pwsh
|
|
|
|
- name: Publish AirAppRuntime
|
|
run: |
|
|
$arch = "${{ matrix.arch }}"
|
|
$publishDir = "publish/airapp-runtime-win-$arch"
|
|
|
|
dotnet publish LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj `
|
|
-c Release `
|
|
-o ./$publishDir `
|
|
--self-contained:false `
|
|
-r win-$arch `
|
|
-p:SelfContained=false `
|
|
-p:PublishAot=false `
|
|
-p:PublishSingleFile=false `
|
|
-p:PublishTrimmed=false `
|
|
-p:PublishReadyToRun=false `
|
|
-p:DebugType=none `
|
|
-p:DebugSymbols=false `
|
|
-p:Version=${{ needs.prepare.outputs.version }} `
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} `
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} `
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
shell: pwsh
|
|
|
|
- name: Publish AirAppHost
|
|
run: |
|
|
$arch = "${{ matrix.arch }}"
|
|
$publishDir = "publish/windows-$arch"
|
|
|
|
dotnet publish LanMountainDesktop.AirAppHost/LanMountainDesktop.AirAppHost.csproj `
|
|
-c Release `
|
|
-o ./$publishDir `
|
|
--self-contained:false `
|
|
-r win-$arch `
|
|
-p:SelfContained=false `
|
|
-p:PublishSingleFile=false `
|
|
-p:DebugType=none `
|
|
-p:DebugSymbols=false `
|
|
-p:PublishTrimmed=false `
|
|
-p:PublishReadyToRun=false `
|
|
-p:BuildingAirAppHost=true `
|
|
-p:SkipAirAppHostBuild=true `
|
|
-p:Version=${{ needs.prepare.outputs.version }} `
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} `
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} `
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
shell: pwsh
|
|
|
|
- name: Restructure for Launcher
|
|
run: |
|
|
$version = "${{ needs.prepare.outputs.version }}"
|
|
$arch = "${{ matrix.arch }}"
|
|
$publishDir = "publish/windows-$arch"
|
|
$launcherPublishDir = "publish/launcher-win-$arch"
|
|
$runtimePublishDir = "publish/airapp-runtime-win-$arch"
|
|
$appDir = "app-$version"
|
|
$newStructure = "publish-launcher/windows-$arch"
|
|
|
|
New-Item -ItemType Directory -Path $newStructure -Force | Out-Null
|
|
$appPath = Join-Path $newStructure $appDir
|
|
Move-Item -Path $publishDir -Destination $appPath -Force
|
|
|
|
if (Test-Path $launcherPublishDir) {
|
|
Copy-Item -Path "$launcherPublishDir\*" -Destination $newStructure -Recurse -Force
|
|
}
|
|
|
|
if (Test-Path $runtimePublishDir) {
|
|
Copy-Item -Path "$runtimePublishDir\*" -Destination $newStructure -Recurse -Force
|
|
}
|
|
|
|
New-Item -ItemType File -Path (Join-Path $appPath ".current") -Force | Out-Null
|
|
|
|
Remove-Item -Path $publishDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item -Path $launcherPublishDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item -Path $runtimePublishDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
Move-Item -Path $newStructure -Destination $publishDir -Force
|
|
shell: pwsh
|
|
|
|
- name: Optimize and Guard Windows Payload
|
|
run: |
|
|
$arch = "${{ matrix.arch }}"
|
|
$publishDir = "publish/windows-$arch"
|
|
|
|
./LanMountainDesktop/scripts/Optimize-PublishPayload.ps1 `
|
|
-PublishDir $publishDir `
|
|
-RuntimeIdentifier "win-$arch" `
|
|
-AssertClean
|
|
shell: pwsh
|
|
|
|
- name: Verify Windows app host payload
|
|
run: |
|
|
$version = "${{ needs.prepare.outputs.version }}"
|
|
$arch = "${{ matrix.arch }}"
|
|
$publishDir = "publish/windows-$arch"
|
|
$appDir = Join-Path $publishDir "app-$version"
|
|
|
|
$requiredFiles = @(
|
|
(Join-Path $publishDir "LanMountainDesktop.Launcher.exe"),
|
|
(Join-Path $publishDir "LanMountainDesktop.AirAppRuntime.exe"),
|
|
(Join-Path $appDir "LanMountainDesktop.exe"),
|
|
(Join-Path $appDir "LanMountainDesktop.AirAppHost.exe")
|
|
)
|
|
|
|
foreach ($path in $requiredFiles) {
|
|
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
|
|
Write-Error "Required release payload file is missing: $path"
|
|
exit 1
|
|
}
|
|
}
|
|
shell: pwsh
|
|
|
|
- name: Install Inno Setup and 7z
|
|
run: |
|
|
choco install innosetup -y --no-progress
|
|
choco install 7zip -y --no-progress
|
|
shell: pwsh
|
|
|
|
- name: Build Installer
|
|
run: |
|
|
$version = "${{ needs.prepare.outputs.version }}"
|
|
$arch = "${{ matrix.arch }}"
|
|
$suffix = "${{ matrix.suffix }}"
|
|
$publishDir = "publish/windows-$arch"
|
|
$outputDir = "build-installer"
|
|
$installerScript = "LanMountainDesktop/installer/LanMountainDesktop.iss"
|
|
|
|
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
|
|
|
|
$candidatePaths = @(
|
|
(Get-Command iscc.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -ErrorAction SilentlyContinue),
|
|
"$env:ProgramFiles(x86)\Inno Setup 6\ISCC.exe",
|
|
"$env:ProgramFiles\Inno Setup 6\ISCC.exe",
|
|
"$env:ChocolateyInstall\lib\innosetup\tools\ISCC.exe"
|
|
) | Where-Object { $_ -and (Test-Path $_) }
|
|
|
|
$isccPath = $candidatePaths | Select-Object -First 1
|
|
if (-not $isccPath) {
|
|
Write-Error "Inno Setup compiler not found."
|
|
exit 1
|
|
}
|
|
|
|
if (-not (Test-Path $installerScript)) {
|
|
Write-Error "Installer script not found: $(Join-Path $PWD $installerScript)"
|
|
exit 1
|
|
}
|
|
|
|
$publishDir = (Resolve-Path $publishDir).Path
|
|
$outputDir = (Resolve-Path $outputDir).Path
|
|
$installerScript = (Resolve-Path $installerScript).Path
|
|
|
|
$compileArgs = @(
|
|
"/DMyAppVersion=$version",
|
|
"/DPublishDir=$publishDir",
|
|
"/DMyOutputDir=$outputDir",
|
|
"/DMyAppArch=$arch",
|
|
"/DMyAppSuffix=$suffix",
|
|
$installerScript
|
|
)
|
|
|
|
& $isccPath @compileArgs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Inno Setup compiler exited with code $LASTEXITCODE"
|
|
exit 1
|
|
}
|
|
|
|
$installerFile = Get-ChildItem -Path $outputDir -Filter "*.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if (-not $installerFile) {
|
|
Write-Error "Failed to create installer"
|
|
exit 1
|
|
}
|
|
shell: pwsh
|
|
|
|
- name: Package Payload Zip
|
|
run: |
|
|
$version = "${{ needs.prepare.outputs.version }}"
|
|
$arch = "${{ matrix.arch }}"
|
|
$payloadRoot = Join-Path $PWD "publish/windows-$arch"
|
|
if (-not (Test-Path $payloadRoot)) {
|
|
Write-Error "Payload root not found: $payloadRoot"
|
|
exit 1
|
|
}
|
|
|
|
$stageDir = Join-Path $PWD "payload-stage/windows-$arch"
|
|
$releaseDir = Join-Path $PWD "release-assets"
|
|
Remove-Item -Path $stageDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
New-Item -ItemType Directory -Path $stageDir -Force | Out-Null
|
|
New-Item -ItemType Directory -Path $releaseDir -Force | Out-Null
|
|
|
|
Get-ChildItem -Path $payloadRoot -Recurse -File | ForEach-Object {
|
|
$relative = [System.IO.Path]::GetRelativePath($payloadRoot, $_.FullName).Replace('\', '/')
|
|
if ($relative -eq '.partial' -or $relative -eq '.destroy' -or $relative.StartsWith('.partial/') -or $relative.StartsWith('.destroy/')) {
|
|
return
|
|
}
|
|
|
|
$destination = Join-Path $stageDir ($relative -replace '/', [System.IO.Path]::DirectorySeparatorChar)
|
|
$destinationDir = Split-Path -Path $destination -Parent
|
|
if (-not [string]::IsNullOrWhiteSpace($destinationDir)) {
|
|
New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null
|
|
}
|
|
|
|
Copy-Item -Path $_.FullName -Destination $destination -Force
|
|
}
|
|
|
|
$payloadZip = Join-Path $releaseDir "files-windows-$arch.zip"
|
|
if (Test-Path $payloadZip) {
|
|
Remove-Item $payloadZip -Force
|
|
}
|
|
Compress-Archive -Path (Join-Path $stageDir '*') -DestinationPath $payloadZip -Force
|
|
shell: pwsh
|
|
|
|
- name: Upload Release Assets
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: release-windows-${{ matrix.arch }}
|
|
path: |
|
|
release-assets/files-windows-${{ matrix.arch }}.zip
|
|
build-installer/*.exe
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
build-linux:
|
|
needs: prepare
|
|
runs-on: ubuntu-latest
|
|
name: Build_Linux
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
submodules: recursive
|
|
ref: ${{ needs.prepare.outputs.checkout_ref }}
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
libfontconfig1 libfreetype6 \
|
|
libx11-6 libxrandr2 libxinerama1 \
|
|
libxi6 libxcursor1 libxext6 \
|
|
libxrender1 libxkbcommon-x11-0 \
|
|
clang zlib1g-dev zip rsync
|
|
|
|
sudo apt-get install -y libasound2t64 || sudo apt-get install -y libasound2
|
|
sudo apt-get install -y libportaudio2t64 || sudo apt-get install -y libportaudio2
|
|
sudo apt-get install -y libwebkit2gtk-4.1-dev || sudo apt-get install -y libwebkit2gtk-4.0-dev
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
dotnet-quality: 'preview'
|
|
|
|
- name: Stamp release version metadata
|
|
shell: pwsh
|
|
run: |
|
|
./scripts/Set-ReleaseVersion.ps1 `
|
|
-Version "${{ needs.prepare.outputs.version }}" `
|
|
-AssemblyVersion "${{ needs.prepare.outputs.assembly_version }}"
|
|
|
|
- name: Restore
|
|
run: dotnet restore ${{ env.Solution_Name }}
|
|
|
|
- name: Build
|
|
run: >
|
|
dotnet build ${{ env.Solution_Name }} -c Release --no-restore -v minimal
|
|
-p:Version=${{ needs.prepare.outputs.version }}
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }}
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }}
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Publish Launcher (AOT)
|
|
run: |
|
|
dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \
|
|
-c Release \
|
|
-o ./publish/launcher-linux-x64 \
|
|
--self-contained \
|
|
-r linux-x64 \
|
|
-p:PublishAot=true \
|
|
-p:PublishSingleFile=true \
|
|
-p:IncludeNativeLibrariesForSelfExtract=true \
|
|
-p:EnableCompressionInSingleFile=true \
|
|
-p:DebugType=none \
|
|
-p:DebugSymbols=false \
|
|
-p:Version=${{ needs.prepare.outputs.version }} \
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Publish Main App
|
|
run: |
|
|
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj \
|
|
-c Release \
|
|
-o ./publish/linux-x64-app \
|
|
--self-contained \
|
|
-r linux-x64 \
|
|
-p:PublishSingleFile=false \
|
|
-p:SelfContained=true \
|
|
-p:DebugType=none \
|
|
-p:DebugSymbols=false \
|
|
-p:SkipAirAppHostBuild=true \
|
|
-p:PublishTrimmed=false \
|
|
-p:PublishReadyToRun=false \
|
|
-p:Version=${{ needs.prepare.outputs.version }} \
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Publish AirAppRuntime
|
|
run: |
|
|
dotnet publish LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj \
|
|
-c Release \
|
|
-o ./publish/airapp-runtime-linux-x64 \
|
|
--self-contained false \
|
|
-r linux-x64 \
|
|
-p:SelfContained=false \
|
|
-p:PublishAot=false \
|
|
-p:PublishSingleFile=false \
|
|
-p:PublishTrimmed=false \
|
|
-p:PublishReadyToRun=false \
|
|
-p:DebugType=none \
|
|
-p:DebugSymbols=false \
|
|
-p:Version=${{ needs.prepare.outputs.version }} \
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Restructure for Launcher
|
|
run: |
|
|
version="${{ needs.prepare.outputs.version }}"
|
|
publishDir="publish/linux-x64"
|
|
appDir="app-$version"
|
|
launcherDir="publish/launcher-linux-x64"
|
|
runtimeDir="publish/airapp-runtime-linux-x64"
|
|
|
|
mkdir -p "$publishDir"
|
|
mv "publish/linux-x64-app" "$publishDir/$appDir"
|
|
|
|
if [ -d "$launcherDir" ]; then
|
|
cp -r "$launcherDir"/* "$publishDir/"
|
|
chmod +x "$publishDir/LanMountainDesktop.Launcher" 2>/dev/null || true
|
|
fi
|
|
|
|
if [ -d "$runtimeDir" ]; then
|
|
cp -r "$runtimeDir"/* "$publishDir/"
|
|
chmod +x "$publishDir/LanMountainDesktop.AirAppRuntime" 2>/dev/null || true
|
|
fi
|
|
|
|
touch "$publishDir/$appDir/.current"
|
|
rm -rf "$launcherDir" "$runtimeDir"
|
|
|
|
- name: Package as DEB
|
|
run: |
|
|
version="${{ needs.prepare.outputs.version }}"
|
|
source="publish/linux-x64"
|
|
package_name="LanMountainDesktop"
|
|
package_version="${version}"
|
|
arch="amd64"
|
|
desktop_template="LanMountainDesktop/packaging/linux/LanMountainDesktop.desktop"
|
|
icon_source="LanMountainDesktop/packaging/linux/lanmountaindesktop.png"
|
|
|
|
mkdir -p "build-deb/DEBIAN"
|
|
mkdir -p "build-deb/usr/local/bin"
|
|
mkdir -p "build-deb/usr/share/applications"
|
|
mkdir -p "build-deb/usr/share/pixmaps"
|
|
mkdir -p "build-deb/usr/share/icons/hicolor/256x256/apps"
|
|
|
|
cp -r "$source"/* "build-deb/usr/local/bin/"
|
|
|
|
sed \
|
|
-e "s|@@EXEC@@|/usr/local/bin/LanMountainDesktop.Launcher|g" \
|
|
-e "s|@@ICON@@|lanmountaindesktop|g" \
|
|
"$desktop_template" > "build-deb/usr/share/applications/LanMountainDesktop.desktop"
|
|
|
|
cp "$icon_source" "build-deb/usr/share/pixmaps/lanmountaindesktop.png"
|
|
cp "$icon_source" "build-deb/usr/share/icons/hicolor/256x256/apps/lanmountaindesktop.png"
|
|
|
|
{
|
|
printf '%s\n' '#!/bin/sh'
|
|
printf '%s\n' 'set -e'
|
|
printf '%s\n' 'if command -v update-desktop-database >/dev/null 2>&1; then'
|
|
printf '%s\n' ' update-desktop-database /usr/share/applications >/dev/null 2>&1 || true'
|
|
printf '%s\n' 'fi'
|
|
printf '%s\n' 'if command -v gtk-update-icon-cache >/dev/null 2>&1; then'
|
|
printf '%s\n' ' gtk-update-icon-cache /usr/share/icons/hicolor >/dev/null 2>&1 || true'
|
|
printf '%s\n' 'fi'
|
|
} > "build-deb/DEBIAN/postinst"
|
|
|
|
{
|
|
printf '%s\n' "Package: $package_name"
|
|
printf '%s\n' "Version: $package_version"
|
|
printf '%s\n' "Architecture: $arch"
|
|
printf '%s\n' 'Maintainer: LanMountain Team <dev@example.com>'
|
|
printf '%s\n' 'Description: LanMountain Desktop Application'
|
|
printf '%s\n' ' A desktop application for LanMountain.'
|
|
} > "build-deb/DEBIAN/control"
|
|
|
|
chmod 755 "build-deb/usr/local/bin/LanMountainDesktop.Launcher" 2>/dev/null || chmod 755 "build-deb/usr/local/bin"/*
|
|
chmod 644 "build-deb/usr/share/applications/LanMountainDesktop.desktop"
|
|
chmod 644 "build-deb/usr/share/pixmaps/lanmountaindesktop.png"
|
|
chmod 644 "build-deb/usr/share/icons/hicolor/256x256/apps/lanmountaindesktop.png"
|
|
chmod 755 "build-deb/DEBIAN/postinst"
|
|
|
|
dpkg-deb --build "build-deb" "${package_name}_${package_version}_${arch}.deb"
|
|
|
|
- name: Package Payload Zip
|
|
run: |
|
|
version="${{ needs.prepare.outputs.version }}"
|
|
payload_root="publish/linux-x64/app-$version"
|
|
release_dir="$PWD/release-assets"
|
|
stage_dir="$PWD/payload-stage/linux-x64"
|
|
|
|
if [ ! -d "$payload_root" ]; then
|
|
echo "Payload root not found: $payload_root"
|
|
exit 1
|
|
fi
|
|
|
|
rm -rf "$stage_dir"
|
|
mkdir -p "$stage_dir" "$release_dir"
|
|
rsync -a \
|
|
--exclude '.current' \
|
|
--exclude '.partial' \
|
|
--exclude '.destroy' \
|
|
"$payload_root/" "$stage_dir/"
|
|
|
|
(
|
|
cd "$stage_dir"
|
|
zip -qr "$release_dir/files-linux-x64.zip" .
|
|
)
|
|
|
|
- name: Upload Release Assets
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: release-linux-x64
|
|
path: |
|
|
release-assets/files-linux-x64.zip
|
|
*.deb
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
build-macos:
|
|
needs: prepare
|
|
runs-on: macos-latest
|
|
continue-on-error: true
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
arch: [x64, arm64]
|
|
name: Build_macOS_${{ matrix.arch }}
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
submodules: recursive
|
|
ref: ${{ needs.prepare.outputs.checkout_ref }}
|
|
|
|
- name: Install dependencies
|
|
run: brew install portaudio
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
dotnet-quality: 'preview'
|
|
|
|
- name: Stamp release version metadata
|
|
shell: pwsh
|
|
run: |
|
|
./scripts/Set-ReleaseVersion.ps1 `
|
|
-Version "${{ needs.prepare.outputs.version }}" `
|
|
-AssemblyVersion "${{ needs.prepare.outputs.assembly_version }}"
|
|
|
|
- name: Restore
|
|
run: dotnet restore ${{ env.Solution_Name }}
|
|
|
|
- name: Build
|
|
run: >
|
|
dotnet build ${{ env.Solution_Name }} -c Release --no-restore -v minimal
|
|
-p:Version=${{ needs.prepare.outputs.version }}
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }}
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }}
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Publish Launcher (AOT)
|
|
run: |
|
|
dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \
|
|
-c Release \
|
|
-o ./publish/launcher-macos-${{ matrix.arch }} \
|
|
--self-contained \
|
|
-r osx-${{ matrix.arch }} \
|
|
-p:PublishAot=true \
|
|
-p:PublishSingleFile=true \
|
|
-p:IncludeNativeLibrariesForSelfExtract=true \
|
|
-p:EnableCompressionInSingleFile=true \
|
|
-p:DebugType=none \
|
|
-p:DebugSymbols=false \
|
|
-p:Version=${{ needs.prepare.outputs.version }} \
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Publish Main App
|
|
run: |
|
|
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj \
|
|
-c Release \
|
|
-o ./publish/macos-${{ matrix.arch }}-app \
|
|
--self-contained:false \
|
|
-r osx-${{ matrix.arch }} \
|
|
-p:SelfContained=false \
|
|
-p:PublishSingleFile=false \
|
|
-p:DebugType=none \
|
|
-p:DebugSymbols=false \
|
|
-p:SkipAirAppHostBuild=true \
|
|
-p:PublishTrimmed=false \
|
|
-p:PublishReadyToRun=false \
|
|
-p:Version=${{ needs.prepare.outputs.version }} \
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Publish AirAppRuntime
|
|
run: |
|
|
dotnet publish LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj \
|
|
-c Release \
|
|
-o ./publish/airapp-runtime-macos-${{ matrix.arch }} \
|
|
--self-contained false \
|
|
-r osx-${{ matrix.arch }} \
|
|
-p:SelfContained=false \
|
|
-p:PublishAot=false \
|
|
-p:PublishSingleFile=false \
|
|
-p:PublishTrimmed=false \
|
|
-p:PublishReadyToRun=false \
|
|
-p:DebugType=none \
|
|
-p:DebugSymbols=false \
|
|
-p:Version=${{ needs.prepare.outputs.version }} \
|
|
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
|
|
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
|
|
|
|
- name: Optimize and Guard macOS Payload
|
|
run: |
|
|
arch="${{ matrix.arch }}"
|
|
publishDir="publish/macos-${arch}-app"
|
|
|
|
pwsh ./LanMountainDesktop/scripts/Optimize-PublishPayload.ps1 \
|
|
-PublishDir "$publishDir" \
|
|
-RuntimeIdentifier "osx-${arch}" \
|
|
-AssertClean
|
|
shell: bash
|
|
|
|
- name: Package Payload Zip
|
|
run: |
|
|
release_dir="$PWD/release-assets"
|
|
stage_dir="$PWD/payload-stage/macos-${{ matrix.arch }}"
|
|
payload_root="publish/macos-${{ matrix.arch }}-app"
|
|
|
|
rm -rf "$stage_dir"
|
|
mkdir -p "$stage_dir" "$release_dir"
|
|
rsync -a "$payload_root/" "$stage_dir/"
|
|
(
|
|
cd "$stage_dir"
|
|
zip -qr "$release_dir/files-macos-${{ matrix.arch }}.zip" .
|
|
)
|
|
|
|
- name: Restructure and Package as DMG
|
|
continue-on-error: true
|
|
run: |
|
|
version="${{ needs.prepare.outputs.version }}"
|
|
arch="${{ matrix.arch }}"
|
|
app_name="LanMountainDesktop"
|
|
package_name="${app_name}-${version}-macos-${arch}"
|
|
launcherDir="publish/launcher-macos-$arch"
|
|
runtimeDir="publish/airapp-runtime-macos-$arch"
|
|
appSourceDir="publish/macos-$arch-app"
|
|
|
|
mkdir -p "${app_name}.app/Contents/MacOS"
|
|
appDir="app-$version"
|
|
mkdir -p "${app_name}.app/Contents/MacOS/$appDir"
|
|
|
|
cp -r "$appSourceDir"/* "${app_name}.app/Contents/MacOS/$appDir/"
|
|
if [ -d "$launcherDir" ]; then
|
|
cp -r "$launcherDir"/* "${app_name}.app/Contents/MacOS/"
|
|
chmod +x "${app_name}.app/Contents/MacOS/LanMountainDesktop.Launcher" 2>/dev/null || true
|
|
fi
|
|
|
|
if [ -d "$runtimeDir" ]; then
|
|
cp -r "$runtimeDir"/* "${app_name}.app/Contents/MacOS/"
|
|
chmod +x "${app_name}.app/Contents/MacOS/LanMountainDesktop.AirAppRuntime" 2>/dev/null || true
|
|
fi
|
|
|
|
touch "${app_name}.app/Contents/MacOS/$appDir/.current"
|
|
mkdir -p "${app_name}.app/Contents/Resources"
|
|
|
|
{
|
|
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>'
|
|
printf '%s\n' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
|
|
printf '%s\n' '<plist version="1.0">'
|
|
printf '%s\n' '<dict>'
|
|
printf '%s\n' ' <key>CFBundleExecutable</key>'
|
|
printf '%s\n' ' <string>LanMountainDesktop.Launcher</string>'
|
|
printf '%s\n' ' <key>CFBundleName</key>'
|
|
printf '%s\n' ' <string>LanMountain Desktop</string>'
|
|
printf '%s\n' ' <key>CFBundleVersion</key>'
|
|
printf '%s\n' " <string>$version</string>"
|
|
printf '%s\n' ' <key>CFBundleShortVersionString</key>'
|
|
printf '%s\n' " <string>$version</string>"
|
|
printf '%s\n' ' <key>CFBundleIdentifier</key>'
|
|
printf '%s\n' ' <string>com.lanmountain.desktop</string>'
|
|
printf '%s\n' ' <key>CFBundlePackageType</key>'
|
|
printf '%s\n' ' <string>APPL</string>'
|
|
printf '%s\n' '</dict>'
|
|
printf '%s\n' '</plist>'
|
|
} > "${app_name}.app/Contents/Info.plist"
|
|
|
|
mkdir -p dmg-temp
|
|
cp -r "${app_name}.app" dmg-temp/
|
|
hdiutil create -volname "${app_name}" -srcfolder dmg-temp -ov -format UDZO "${package_name}.dmg"
|
|
|
|
- name: Upload Release Assets
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: release-macos-${{ matrix.arch }}
|
|
path: |
|
|
release-assets/files-macos-${{ matrix.arch }}.zip
|
|
*.dmg
|
|
if-no-files-found: ignore
|
|
retention-days: 30
|
|
|
|
github-release:
|
|
needs: [prepare, build-windows, build-linux]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Download release artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: release-files
|
|
pattern: release-*
|
|
merge-multiple: true
|
|
|
|
- name: Normalize release files
|
|
run: |
|
|
mkdir -p release-bundle
|
|
|
|
mapfile -t downloaded_files < <(find release-files -type f)
|
|
if [ "${#downloaded_files[@]}" -eq 0 ]; then
|
|
echo "No downloaded release artifacts were found."
|
|
exit 1
|
|
fi
|
|
|
|
for file in "${downloaded_files[@]}"; do
|
|
base_name="$(basename "$file")"
|
|
target_path="release-bundle/$base_name"
|
|
|
|
if [ -e "$target_path" ]; then
|
|
echo "Duplicate release asset name detected: $base_name"
|
|
echo "Conflicting file: $file"
|
|
exit 1
|
|
fi
|
|
|
|
cp "$file" "$target_path"
|
|
done
|
|
|
|
- name: Validate release files
|
|
run: |
|
|
echo "Release files:"
|
|
find release-bundle -maxdepth 1 -type f -exec ls -lh {} \;
|
|
|
|
if [ ! -f release-bundle/files-windows-x64.zip ] || [ ! -f release-bundle/files-windows-x86.zip ] || [ ! -f release-bundle/files-linux-x64.zip ]; then
|
|
echo "Required payload zips are missing."
|
|
exit 1
|
|
fi
|
|
|
|
file_count=$(find release-bundle -maxdepth 1 -type f | wc -l)
|
|
if [ "$file_count" -eq 0 ]; then
|
|
echo "No release files were produced."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Create or Update Release
|
|
uses: ncipollo/release-action@v1.21.0
|
|
with:
|
|
tag: ${{ needs.prepare.outputs.tag }}
|
|
name: ${{ needs.prepare.outputs.tag }}
|
|
allowUpdates: true
|
|
draft: false
|
|
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
|
|
artifacts: 'release-bundle/*'
|
|
body: |
|
|
## Release ${{ needs.prepare.outputs.version }}
|
|
|
|
### Installers
|
|
- `LanMountainDesktop-Setup-${{ needs.prepare.outputs.version }}-x64.exe`
|
|
- `LanMountainDesktop-Setup-${{ needs.prepare.outputs.version }}-x86.exe`
|
|
- `LanMountainDesktop_${{ needs.prepare.outputs.version }}_amd64.deb`
|
|
|
|
### Payload Archives
|
|
- `files-windows-x64.zip`
|
|
- `files-windows-x86.zip`
|
|
- `files-linux-x64.zip`
|
|
|
|
### macOS
|
|
- macOS assets are best-effort and will not block the release.
|
|
|
|
Release keeps only the stable installer and payload outputs. PLONDS delta assets and external mirror metadata are generated by follow-up workflows.
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|