From 7956349c0a34071b27fb8d21bb11c90e3f76a571 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Mon, 24 Nov 2025 00:52:28 -0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=89=93=E5=8C=85):=20=E6=B7=BB=E5=8A=A0W?= =?UTF-8?q?indows=E5=8D=95=E6=96=87=E4=BB=B6=E6=89=93=E5=8C=85=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E5=92=8C=E5=B7=A5=E5=85=B7=E5=AE=89=E8=A3=85=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加install_build_tools.bat用于自动安装构建工具 添加package_windows_single_exe.ps1用于打包Flutter应用为单文件 改进inno_setup.sas增加卸载清理逻辑和进程终止功能 --- install_build_tools.bat | 64 ++++++++++ package_windows_single_exe.ps1 | 168 +++++++++++++++++++++++++++ windows/packaging/exe/inno_setup.sas | 84 ++++++++++++-- 3 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 install_build_tools.bat create mode 100644 package_windows_single_exe.ps1 diff --git a/install_build_tools.bat b/install_build_tools.bat new file mode 100644 index 0000000..04be34d --- /dev/null +++ b/install_build_tools.bat @@ -0,0 +1,64 @@ +@echo off +:: This script checks for and installs all necessary tools for building and packaging the Flutter application on Windows. + +:: 1. Check for Administrator Privileges +net session >nul 2>&1 +if %errorLevel% == 0 ( + echo Administrator privileges detected. Continuing... +) else ( + echo Requesting Administrator privileges to install tools... + powershell -Command "Start-Process cmd.exe -ArgumentList '/c %~s0' -Verb RunAs" >nul 2>&1 + exit /b +) + +:: 2. Check for and Install Chocolatey +echo. +echo === Checking for Chocolatey === +where choco >nul 2>&1 +if %errorlevel% equ 0 ( + echo Chocolatey is already installed. +) else ( + echo Chocolatey not found. Installing now... + powershell -NoProfile -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))" + if %errorlevel% neq 0 ( + echo ERROR: Failed to install Chocolatey. Please install it manually from https://chocolatey.org + pause + exit /b 1 + ) + echo Chocolatey installed successfully. + :: Add Chocolatey to the PATH for the current session + set "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" +) + +:: 3. Install Required Tools via Chocolatey +echo. +echo === Installing Build Tools (7-Zip and Enigma Virtual Box) === + +:: Install 7-Zip +echo Installing 7-Zip... +choco install 7zip -y +if %errorlevel% neq 0 ( + echo ERROR: Failed to install 7-Zip. + pause + exit /b 1 +) +echo 7-Zip installed successfully. + +:: Install Enigma Virtual Box +echo Installing Enigma Virtual Box... +choco install enigma-virtual-box -y +if %errorlevel% neq 0 ( + echo ERROR: Failed to install Enigma Virtual Box. + pause + exit /b 1 +) +echo Enigma Virtual Box installed successfully. + + +echo. +echo ======================================================= +echo All required build tools have been installed. +echo You can now use 'package_windows_single_exe.ps1' to build your single-file executable. +echo ======================================================= +echo. +pause \ No newline at end of file diff --git a/package_windows_single_exe.ps1 b/package_windows_single_exe.ps1 new file mode 100644 index 0000000..64cb0c9 --- /dev/null +++ b/package_windows_single_exe.ps1 @@ -0,0 +1,168 @@ +#Requires -RunAsAdministrator +<# +.SYNOPSIS + Builds and packages a Flutter Windows application into a single executable. + +.DESCRIPTION + This script automates the entire process of creating a single-file executable for a Flutter Windows project. + It performs the following steps: + 1. Checks for and installs Chocolatey if not present. + 2. Installs Enigma Virtual Box and 7-Zip using Chocolatey. + 3. Builds the Flutter application in release mode. + 4. Packages the build output into a single EXE using Enigma Virtual Box. + 5. If Enigma fails, it falls back to creating a 7-Zip self-extracting archive. + 6. Places the final packaged executable in the 'dist' directory. + +.NOTES + - This script must be run with Administrator privileges to install software. + - An internet connection is required for the first run to download and install dependencies. +#> + +# --- Configuration --- +$ErrorActionPreference = "Stop" +$buildPath = ".\build\windows\x64\runner\Release" +$outputPath = ".\dist" + +# --- Helper Functions --- +function Test-CommandExists { + param($command) + return (Get-Command $command -ErrorAction SilentlyContinue) +} + +function Install-Chocolatey { + if (Test-CommandExists "choco") { + Write-Host "✅ Chocolatey is already installed." + return + } + + Write-Host "Chocolatey not found. Installing..." + try { + Set-ExecutionPolicy Bypass -Scope Process -Force + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 + iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + Write-Host "✅ Chocolatey installed successfully." + } catch { + Write-Host "❌ Failed to install Chocolatey. Please install it manually and re-run the script." + exit 1 + } +} + +function Install-Tools { + Write-Host "Checking for required tools (Enigma Virtual Box, 7-Zip)..." + $tools = @("enigma-virtual-box", "7zip") + foreach ($tool in $tools) { + if (choco list --local-only --exact $tool) { + Write-Host "✅ $tool is already installed." + } else { + Write-Host "Installing $tool..." + try { + choco install $tool -y --force + Write-Host "✅ $tool installed successfully." + } catch { + Write-Host "❌ Failed to install $tool." + exit 1 + } + } + } +} + +# --- Main Script --- + +# 1. Setup Environment +Write-Host "=== 1/4: Setting up build environment ===" +Install-Chocolatey +Install-Tools + +# 2. Build Flutter App +Write-Host "=== 2/4: Building Flutter Windows application (Release) ===" +try { + flutter build windows --release +} catch { + Write-Host "❌ Flutter build failed." + exit 1 +} + +# 3. Package Application +Write-Host "=== 3/4: Packaging into a single executable ===" +if (-not (Test-Path $buildPath)) { + Write-Host "❌ Build directory not found: $buildPath" + exit 1 +} + +# Create output directory +New-Item -ItemType Directory -Path $outputPath -Force | Out-Null + +# Get main executable +$exeFile = Get-ChildItem -Path $buildPath -Filter "*.exe" | Select-Object -First 1 +if (-not $exeFile) { + Write-Host "❌ No executable found in build directory." + exit 1 +} + +$inputExe = $exeFile.FullName +$outputExe = "$outputPath\$($exeFile.BaseName)_Single.exe" +$enigmaCliPath = "C:\Program Files\Enigma Virtual Box\enigmavb.exe" +$packageSuccess = $false + +# Attempt to package with Enigma Virtual Box +if (Test-Path $enigmaCliPath) { + Write-Host "Attempting to package with Enigma Virtual Box..." + try { + & $enigmaCliPath /quiet /project "$outputPath\project.evb" /input "$inputExe" /output "$outputExe" /folder "$buildPath" /compress 3 /deleteext + + if ($?) { + Write-Host "✅ Enigma Virtual Box packaging successful." + $packageSuccess = $true + } else { + Write-Host "⚠️ Enigma Virtual Box packaging failed. Exit code: $lastexitcode" + } + } catch { + Write-Host "⚠️ An error occurred during Enigma Virtual Box packaging." + } +} else { + Write-Host "⚠️ Enigma Virtual Box not found at $enigmaCliPath." +} + +# 4. Fallback to 7-Zip if Enigma failed +if (-not $packageSuccess) { + Write-Host "=== 4/4: Fallback: Packaging with 7-Zip SFX ===" + $7zipCliPath = "C:\Program Files\7-Zip\7z.exe" + $outputSfx = "$outputPath\$($exeFile.BaseName)_Package.exe" + + if (-not (Test-Path $7zipCliPath)) { + Write-Host "❌ 7-Zip not found. Cannot create self-extracting archive." + exit 1 + } + + $sfxConfig = @' +;!@Install@!UTF-8! +Title="Flutter Application" +BeginPrompt="Do you want to extract and run the application?" +RunProgram="%%T\%%S\$($exeFile.Name)" +;!@InstallEnd@! +'@ + $sfxConfigFile = "$outputPath\sfx_config.txt" + Set-Content -Path $sfxConfigFile -Value $sfxConfig + + try { + & $7zipCliPath a -sfx7z.sfx "$outputSfx" "$buildPath\*" -r "-i!$sfxConfigFile" + Write-Host "✅ 7-Zip self-extracting archive created successfully." + $outputExe = $outputSfx + $packageSuccess = $true + } catch { + Write-Host "❌ 7-Zip packaging failed." + exit 1 + } +} + +# --- Final Summary --- +if ($packageSuccess) { + $finalSize = (Get-Item $outputExe).Length / 1MB + Write-Host "================================================" + Write-Host "✅ Packaging Complete!" + Write-Host " Output file: $outputExe" + Write-Host " Size: $([math]::Round($finalSize, 2)) MB" + Write-Host "================================================" +} else { + Write-Host "❌ All packaging attempts failed." +} \ No newline at end of file diff --git a/windows/packaging/exe/inno_setup.sas b/windows/packaging/exe/inno_setup.sas index 7ab7532..68abc92 100755 --- a/windows/packaging/exe/inno_setup.sas +++ b/windows/packaging/exe/inno_setup.sas @@ -49,7 +49,7 @@ CloseApplications=force {% endfor %} [Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if CREATE_DESKTOP_ICON != true %}unchecked{% else %}checkedonce{% endif %} +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if LAUNCH_AT_STARTUP != true %}unchecked{% else %}checkedonce{% endif %} [Files] Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs @@ -62,14 +62,82 @@ Name: "{userstartup}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; [Run] Filename: "{app}\\{{EXECUTABLE_NAME}}"; Description: "{cm:LaunchProgram,{{DISPLAY_NAME}}}"; Flags: {% if PRIVILEGES_REQUIRED == 'admin' %}runascurrentuser{% endif %} nowait postinstall skipifsilent +[UninstallDelete] +Type: filesandordirs; Name: "{app}" +Type: filesandordirs; Name: "{localappdata}\\{{DISPLAY_NAME}}" +Type: filesandordirs; Name: "{userappdata}\\{{DISPLAY_NAME}}" +Type: filesandordirs; Name: "{tmp}\\{{DISPLAY_NAME}}" + +[Registry] +Root: HKCU; Subkey: "Software\\{{DISPLAY_NAME}}"; Flags: uninsdeletekey +Root: HKLM; Subkey: "Software\\{{DISPLAY_NAME}}"; Flags: uninsdeletekey [Code] -function InitializeSetup(): Boolean; -var - ResultCode: Integer; +procedure AppendLog(S: string); begin - Exec('taskkill', '/F /IM BearVPN.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) - Exec('net', 'stop "BearVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) - Exec('sc.exe', 'delete "BearVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) + SaveStringToFile(ExpandConstant('{tmp}\\{{DISPLAY_NAME}}_installer.log'), S + #13#10, True); +end; + +procedure TerminateProcesses(); +var ResultCode: Integer; +begin + try + Exec('taskkill', '/F /IM {{EXECUTABLE_NAME}}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + except + AppendLog('terminate failed: {{EXECUTABLE_NAME}}'); + end; + try + Exec('taskkill', '/F /IM BearVPNCli.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + except + AppendLog('terminate failed: BearVPNCli.exe'); + end; +end; + +procedure StopServices(); +var ResultCode: Integer; +begin + try + Exec('net', 'stop "BearVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec('sc.exe', 'delete "BearVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + except + AppendLog('service stop/delete failed: BearVPNTunnelService'); + end; +end; + +procedure CleanPaths(); +var ok: Boolean; +begin + try + if DirExists(ExpandConstant('{app}')) then + ok := DelTree(ExpandConstant('{app}'), True, True, True) + else ok := True; + if not ok then AppendLog('delete failed: {app}'); + except + AppendLog('exception deleting: {app}'); + end; + try + if DirExists(ExpandConstant('{localappdata}\\{{DISPLAY_NAME}}')) then + ok := DelTree(ExpandConstant('{localappdata}\\{{DISPLAY_NAME}}'), True, True, True); + if DirExists(ExpandConstant('{userappdata}\\{{DISPLAY_NAME}}')) then + ok := DelTree(ExpandConstant('{userappdata}\\{{DISPLAY_NAME}}'), True, True, True); + if DirExists(ExpandConstant('{tmp}\\{{DISPLAY_NAME}}')) then + ok := DelTree(ExpandConstant('{tmp}\\{{DISPLAY_NAME}}'), True, True, True); + except + AppendLog('exception deleting user data'); + end; +end; + +function InitializeSetup(): Boolean; +begin + TerminateProcesses(); + StopServices(); Result := True; -end; \ No newline at end of file +end; + +function InitializeUninstall(): Boolean; +begin + TerminateProcesses(); + StopServices(); + CleanPaths(); + Result := True; +end;