arblant
firefox-vpn-client / scripts / build-windows.ps1
build-windows.ps198 lines · 5.3 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
# Builds the Windows app with its core into dist\:
# - Firefox-VPN-<version>\: firefox-vpn.exe (the Flutter app),
#   core\firefox-vpn-core.exe (the Go core, which the app starts with -app),
#   and core\firefox-vpn-tun.exe (the TUN service, which the app or Setup
#   installs; scripts\build-windows-tun.ps1 says how it is signed), to run
#   here;
# - Firefox-VPN-<version>-Windows-x64.zip: the portable app, a "Firefox VPN"
#   folder to unpack anywhere (or over an older one);
# - Firefox-VPN-<version>-Windows-Setup-x64.exe: the installer
#   (app\windows\installer\firefox-vpn.iss, Inno Setup 6).
# The app keeps its data (accounts, settings, logs) in user-data beside it.
# The zip and the installer are made from a clean copy, never with a
# user-data folder. The version is "major.minor.patch" from app\pubspec.yaml,
# or -AppVersion (to make a test release for the app's updates); a folder that
# already exists is refused, because a running build locks its files.
# The core and the TUN service get the public half of the update signing key
# (scripts\signing-key.ps1): the app installs only releases whose
# SHA256SUMS.txt its private half signed.
# PowerShell 7: Windows PowerShell 5.1 writes zip paths with backslashes.
#Requires -Version 7
param([string]$AppVersion)
$ErrorActionPreference = 'Stop'

$root = Split-Path -Parent $PSScriptRoot
$match = Select-String -Path "$root\app\pubspec.yaml" -Pattern '^version:\s*(\d+\.\d+\.\d+)'
if (-not $match) { throw 'No version in app\pubspec.yaml' }
$version = $match.Matches[0].Groups[1].Value
if ($AppVersion) { $version = $AppVersion }
$out = "$root\dist\Firefox-VPN-$version"
if (Test-Path $out) { throw "$out already exists; bump the version in app\pubspec.yaml" }

# Inno Setup's compiler: ISCC on the PATH, else its usual places.
$iscc = (Get-Command ISCC.exe -ErrorAction SilentlyContinue).Source
if (-not $iscc) {
    $iscc = @("$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe", "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
        "$env:ProgramFiles\Inno Setup 6\ISCC.exe") | Where-Object { Test-Path $_ } | Select-Object -First 1
}
if (-not $iscc) { throw 'Inno Setup 6 is needed for the installer (winget install JRSoftware.InnoSetup)' }

Push-Location "$root\app"
try {
    # Flutter copies the assets into the build folder but never removes the
    # ones taken out of the app; start from empty copies so old files (such
    # as earlier PNG flags) do not ship.
    foreach ($stale in "build\flutter_assets", "build\windows\x64\runner\Release\data\flutter_assets") {
        if (Test-Path $stale) { Remove-Item -LiteralPath $stale -Recurse -Force }
    }
    # The packages from the local cache when it has them; from pub.dev
    # otherwise (a new machine). pub.dev refuses some requests from some
    # countries (its security advisories), which an offline get avoids.
    flutter pub get --offline
    if ($LASTEXITCODE -ne 0) { flutter pub get }
    if ($LASTEXITCODE -ne 0) { throw 'flutter pub get failed' }
    flutter build windows --release --no-pub --dart-define=APP_VERSION=$version
    if ($LASTEXITCODE -ne 0) { throw 'flutter build failed' }
} finally { Pop-Location }

New-Item -ItemType Directory -Force "$root\dist" | Out-Null
Copy-Item "$root\app\build\windows\x64\runner\Release" $out -Recurse
# Without it the window stays empty on copies of Windows that lack their own
# (app\windows\CMakeLists.txt copies it from the Windows SDK).
if (-not (Test-Path "$out\d3dcompiler_47.dll")) {
    throw 'd3dcompiler_47.dll is not in the build: install the Windows SDK (Visual Studio Installer, C++ desktop development)'
}

$public = & "$PSScriptRoot\signing-key.ps1"
Push-Location "$root\core"
try {
    # The executable's icon and version information (Windows resources).
    $env:APP_VERSION = $version
    go generate ./cmd/firefox-vpn-core
    if ($LASTEXITCODE -ne 0) { throw 'go generate failed' }
    $ldflags = "-s -w -X github.com/armanlife/firefox-vpn-client/core/internal/version.Current=$version"
    if ($public) { $ldflags += " -X github.com/armanlife/firefox-vpn-client/core/internal/signing.Key=$public" }
    go build -trimpath -ldflags $ldflags -o "$out\core\firefox-vpn-core.exe" ./cmd/firefox-vpn-core
    if ($LASTEXITCODE -ne 0) { throw 'go build failed' }
    & "$PSScriptRoot\build-windows-tun.ps1" -Version $version -Out "$out\core" -PublicKey $public
} finally { Pop-Location }

# A clean copy named as the user sees it, for the zip and the installer.
$stage = "$root\dist\.stage-$version"
$app = "$stage\Firefox VPN"
Remove-Item -Recurse -Force $stage -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force $app | Out-Null
Get-ChildItem $out -Force | Where-Object { $_.Name -notin 'user-data', 'portable' } | Copy-Item -Destination $app -Recurse
try {
    $zip = "$root\dist\Firefox-VPN-$version-Windows-x64.zip"
    Compress-Archive -Path $app -DestinationPath $zip -CompressionLevel Optimal -Force

    $setupName = "Firefox-VPN-$version-Windows-Setup-x64"
    & $iscc /Q "/DAppVersion=$version" "/DSourceDir=$app" "/DOutputDir=$root\dist" "/DOutputName=$setupName" "$root\app\windows\installer\firefox-vpn.iss"
    if ($LASTEXITCODE -ne 0) { throw 'the installer did not build' }
} finally { Remove-Item -Recurse -Force $stage -ErrorAction SilentlyContinue }

Write-Host "Built $out"
foreach ($f in $zip, "$root\dist\$setupName.exe") {
    Write-Host ("Built {0} ({1:N1} MB)" -f $f, ((Get-Item $f).Length / 1MB))
}