feat(打包): 添加Windows单文件打包脚本和工具安装脚本
添加install_build_tools.bat用于自动安装构建工具 添加package_windows_single_exe.ps1用于打包Flutter应用为单文件 改进inno_setup.sas增加卸载清理逻辑和进程终止功能
This commit is contained in:
parent
8ccfc6d272
commit
7956349c0a
64
install_build_tools.bat
Normal file
64
install_build_tools.bat
Normal file
@ -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
|
||||||
168
package_windows_single_exe.ps1
Normal file
168
package_windows_single_exe.ps1
Normal file
@ -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."
|
||||||
|
}
|
||||||
@ -49,7 +49,7 @@ CloseApplications=force
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
[Tasks]
|
[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 %}
|
Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if LAUNCH_AT_STARTUP != true %}unchecked{% else %}checkedonce{% endif %}
|
||||||
[Files]
|
[Files]
|
||||||
Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
@ -62,14 +62,82 @@ Name: "{userstartup}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}";
|
|||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\\{{EXECUTABLE_NAME}}"; Description: "{cm:LaunchProgram,{{DISPLAY_NAME}}}"; Flags: {% if PRIVILEGES_REQUIRED == 'admin' %}runascurrentuser{% endif %} nowait postinstall skipifsilent
|
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]
|
[Code]
|
||||||
function InitializeSetup(): Boolean;
|
procedure AppendLog(S: string);
|
||||||
var
|
|
||||||
ResultCode: Integer;
|
|
||||||
begin
|
begin
|
||||||
Exec('taskkill', '/F /IM BearVPN.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
SaveStringToFile(ExpandConstant('{tmp}\\{{DISPLAY_NAME}}_installer.log'), S + #13#10, True);
|
||||||
Exec('net', 'stop "BearVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
end;
|
||||||
Exec('sc.exe', 'delete "BearVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
|
|
||||||
|
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;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function InitializeUninstall(): Boolean;
|
||||||
|
begin
|
||||||
|
TerminateProcesses();
|
||||||
|
StopServices();
|
||||||
|
CleanPaths();
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user