diff --git a/.gitignore b/.gitignore index 47ef72d..15db508 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ /Avalonia-API/avalonia-api.db /Avalonia-API/avalonia-api.db-shm /Avalonia-API/avalonia-api.db-wal +/package-output +/package-scripts/tools diff --git a/Avalonia-PC/Avalonia-PC.csproj b/Avalonia-PC/Avalonia-PC.csproj index 46bde58..9be16cf 100644 --- a/Avalonia-PC/Avalonia-PC.csproj +++ b/Avalonia-PC/Avalonia-PC.csproj @@ -4,6 +4,7 @@ net10.0 enable app.manifest + Assets\avalonia-logo.ico true diff --git a/Avalonia-PC/Program.cs b/Avalonia-PC/Program.cs index 4735cba..2283dd5 100644 --- a/Avalonia-PC/Program.cs +++ b/Avalonia-PC/Program.cs @@ -91,9 +91,6 @@ namespace Avalonia_PC public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() -#if DEBUG - .WithDeveloperTools() -#endif .WithInterFont() .LogToTrace(); } diff --git a/package-scripts/installer/Avalonia-PC.iss b/package-scripts/installer/Avalonia-PC.iss new file mode 100644 index 0000000..4e51cde --- /dev/null +++ b/package-scripts/installer/Avalonia-PC.iss @@ -0,0 +1,59 @@ +#ifndef AppName +#define AppName "Avalonia-PC" +#endif +#ifndef AppVersion +#define AppVersion "1.0.0" +#endif +#ifndef AppPublisher +#define AppPublisher "QiCheng" +#endif +#ifndef AppExeName +#define AppExeName "Avalonia-PC.exe" +#endif +#ifndef SourceDir +#define SourceDir "..\..\package-output\publish\Avalonia-PC\win-x64" +#endif +#ifndef OutputDir +#define OutputDir "..\..\package-output\installer" +#endif +#ifndef RepoRoot +#define RepoRoot "..\.." +#endif +#ifndef ChineseLanguageFile +#define ChineseLanguageFile "compiler:Default.isl" +#endif + +[Setup] +AppId={{7E41DD4C-FBF3-4C65-8D9F-4F2D794BC284} +AppName={#AppName} +AppVersion={#AppVersion} +AppPublisher={#AppPublisher} +DefaultDirName={autopf}\{#AppName} +DefaultGroupName={#AppName} +OutputDir={#OutputDir} +OutputBaseFilename={#AppName}-Setup-{#AppVersion}-win-x64 +SetupIconFile={#RepoRoot}\Avalonia-PC\Assets\avalonia-logo.ico +Compression=lzma2 +SolidCompression=yes +WizardStyle=modern +PrivilegesRequired=admin +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +DisableProgramGroupPage=yes +UninstallDisplayIcon={app}\{#AppExeName} + +[Languages] +Name: "chinesesimp"; MessagesFile: "{#ChineseLanguageFile}" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "{#SourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}" +Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent diff --git a/package-scripts/package-pc.bat b/package-scripts/package-pc.bat new file mode 100644 index 0000000..10ff627 --- /dev/null +++ b/package-scripts/package-pc.bat @@ -0,0 +1,32 @@ +@echo off +setlocal + +cd /d "%~dp0.." + +set "APP_VERSION=1.0.0" +set "APP_NAME=Avalonia-PC" +set "APP_PUBLISHER=QiCheng" + +echo Packaging %APP_NAME% %APP_VERSION% for Windows PC... +echo. + +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0package-pc.ps1" -Version "%APP_VERSION%" -AppName "%APP_NAME%" -Publisher "%APP_PUBLISHER%" -SingleFile -InstallInnoSetupIfMissing + +set "EXIT_CODE=%ERRORLEVEL%" +echo. + +if "%EXIT_CODE%"=="0" ( + echo Done. + echo Installer output: %CD%\package-output\installer +) else if "%EXIT_CODE%"=="2" ( + echo Publish completed, but installer was not created because Inno Setup 6 is not installed. + echo This BAT can download Inno Setup into package-scripts\tools. Run it again and allow network access. + echo. + echo Publish output: %CD%\package-output\publish\Avalonia-PC +) else ( + echo Packaging failed. Exit code: %EXIT_CODE% +) + +echo. +pause +exit /b %EXIT_CODE% diff --git a/package-scripts/package-pc.ps1 b/package-scripts/package-pc.ps1 new file mode 100644 index 0000000..67ba6bf --- /dev/null +++ b/package-scripts/package-pc.ps1 @@ -0,0 +1,171 @@ +[CmdletBinding()] +param( + [string]$Configuration = "Release", + [string]$Runtime = "win-x64", + [string]$Version = "1.0.0", + [string]$AppName = "Avalonia-PC", + [string]$Publisher = "QiCheng", + [bool]$SelfContained = $true, + [switch]$SingleFile, + [switch]$InstallInnoSetupIfMissing, + [switch]$SkipInstaller +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $PSScriptRoot +$projectPath = Join-Path $repoRoot "Avalonia-PC\Avalonia-PC.csproj" +$installerScript = Join-Path $PSScriptRoot "installer\Avalonia-PC.iss" +$buildStamp = Get-Date -Format "yyyyMMddHHmmss" +$publishDir = Join-Path $repoRoot "package-output\publish\Avalonia-PC\$Runtime-$buildStamp" +$installerDir = Join-Path $repoRoot "package-output\installer" +$appExeName = "Avalonia-PC.exe" +$toolsDir = Join-Path $PSScriptRoot "tools" +$innoSetupDir = Join-Path $toolsDir "InnoSetup6" +$innoSetupInstaller = Join-Path $toolsDir "downloads\innosetup-6.7.2.exe" +$innoSetupDownloadUrl = "https://github.com/jrsoftware/issrc/releases/download/is-6_7_2/innosetup-6.7.2.exe" +$chineseSimplifiedLanguageFile = Join-Path $innoSetupDir "Languages\ChineseSimplified.isl" +$chineseSimplifiedLanguageUrl = "https://raw.githubusercontent.com/kira-96/Inno-Setup-Chinese-Simplified-Translation/main/ChineseSimplified.isl" + +function Find-InnoSetupCompiler { + $localCompiler = Join-Path $innoSetupDir "ISCC.exe" + if (Test-Path $localCompiler) { + return $localCompiler + } + + $command = Get-Command "iscc" -ErrorAction SilentlyContinue + if ($command) { + return $command.Source + } + + $candidates = @( + "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe", + "$env:ProgramFiles\Inno Setup 6\ISCC.exe", + "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe" + ) + + foreach ($candidate in $candidates) { + if ($candidate -and (Test-Path $candidate)) { + return $candidate + } + } + + return $null +} + +function Install-LocalInnoSetup { + New-Item -ItemType Directory -Force -Path (Split-Path -Parent $innoSetupInstaller), $innoSetupDir | Out-Null + + if (-not (Test-Path $innoSetupInstaller)) { + Write-Host "Downloading Inno Setup 6 to: $innoSetupInstaller" + Invoke-WebRequest -Uri $innoSetupDownloadUrl -OutFile $innoSetupInstaller + } + + Write-Host "Installing local Inno Setup 6 to: $innoSetupDir" + & $innoSetupInstaller /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /CURRENTUSER /DIR="$innoSetupDir" + if ($LASTEXITCODE -ne 0) { + throw "Inno Setup local install failed with exit code $LASTEXITCODE" + } +} + +function Ensure-ChineseSimplifiedLanguageFile { + if (Test-Path $chineseSimplifiedLanguageFile) { + return + } + + $languageDir = Split-Path -Parent $chineseSimplifiedLanguageFile + New-Item -ItemType Directory -Force -Path $languageDir | Out-Null + + Write-Host "Downloading Inno Setup Chinese language file to: $chineseSimplifiedLanguageFile" + Invoke-WebRequest -Uri $chineseSimplifiedLanguageUrl -OutFile $chineseSimplifiedLanguageFile +} + +if (-not (Test-Path $projectPath)) { + throw "Project file not found: $projectPath" +} + +if (-not (Test-Path $installerScript)) { + throw "Installer script not found: $installerScript" +} + +New-Item -ItemType Directory -Force -Path $publishDir, $installerDir | Out-Null + +Write-Host "Publishing $AppName ($Configuration, $Runtime)..." + +$publishArgs = @( + "publish", + $projectPath, + "-c", $Configuration, + "-r", $Runtime, + "--self-contained", $SelfContained.ToString().ToLowerInvariant(), + "-o", $publishDir, + "/p:Version=$Version", + "/p:PublishSingleFile=$($SingleFile.IsPresent.ToString().ToLowerInvariant())", + "/p:IncludeNativeLibrariesForSelfExtract=true", + "/p:PublishTrimmed=false", + "/p:DebugType=None", + "/p:DebugSymbols=false" +) + +dotnet @publishArgs +if ($LASTEXITCODE -ne 0) { + throw "dotnet publish failed with exit code $LASTEXITCODE" +} + +Get-ChildItem -Path $publishDir -Filter "*.pdb" -Recurse -File | Remove-Item -Force + +$publishedExe = Join-Path $publishDir $appExeName +if (-not (Test-Path $publishedExe)) { + throw "Publish completed, but executable was not found: $publishedExe" +} + +Write-Host "Publish output: $publishDir" + +$localInnoCompiler = Join-Path $innoSetupDir "ISCC.exe" + +if ($SkipInstaller) { + Write-Host "SkipInstaller was specified. Installer package was not created." + exit 0 +} + +if ($InstallInnoSetupIfMissing -and -not (Test-Path $localInnoCompiler)) { + Install-LocalInnoSetup +} + +$iscc = Find-InnoSetupCompiler +if (-not $iscc) { + if ($InstallInnoSetupIfMissing) { + Install-LocalInnoSetup + $iscc = Find-InnoSetupCompiler + } + + if (-not $iscc) { + Write-Warning "Inno Setup compiler (ISCC.exe) was not found. Rerun package-scripts\package-pc.bat and let it download Inno Setup into package-scripts\tools." + Write-Host "The publish output is ready at: $publishDir" + exit 2 + } +} + +Write-Host "Building installer with Inno Setup..." +Write-Host "Using Inno Setup compiler: $iscc" +Ensure-ChineseSimplifiedLanguageFile + +$isccArgs = @( + "/DAppName=$AppName", + "/DAppVersion=$Version", + "/DAppPublisher=$Publisher", + "/DAppExeName=$appExeName", + "/DSourceDir=$publishDir", + "/DOutputDir=$installerDir", + "/DRepoRoot=$repoRoot", + "/DChineseLanguageFile=$chineseSimplifiedLanguageFile", + $installerScript +) + +& $iscc @isccArgs +if ($LASTEXITCODE -ne 0) { + throw "Inno Setup failed with exit code $LASTEXITCODE" +} + +$setupFile = Join-Path $installerDir "$AppName-Setup-$Version-$Runtime.exe" +Write-Host "Installer created: $setupFile"