Files
luma/LumaGtk/scripts/windows/setup-env.ps1
Ole André Vadla Ravnås deebeb8ecd gtk: Build on Windows in a fixed environment
SwiftPM rebuilds everything when the environment changes. Each run
grew LIB in the caller's shell, and each terminal brings its own
variables, so no build was ever a no-op.

Build from an allowlisted environment and hand the caller's back
afterwards.
2026-09-25 18:50:42 +02:00

237 lines
10 KiB
PowerShell

# Set environment variables needed to build LumaGtk on Windows.
#
# This script is intended to be dot-sourced from build.ps1 or from an
# interactive PowerShell prompt when iterating on the build manually.
#
# . .\scripts\windows\setup-env.ps1
#
# Prefix locations can be overridden via -VcpkgPrefix / -FridaPrefix /
# -R2Prefix / -PharoPrefix, or via the VCPKG_PREFIX / FRIDA_PREFIX /
# R2_PREFIX / PHARO_PREFIX env vars.
[CmdletBinding()]
param(
[string] $VcpkgPrefix,
[string] $FridaPrefix,
[string] $R2Prefix,
[string] $PharoPrefix
)
$ErrorActionPreference = 'Stop'
function Resolve-Prefix {
param([string] $Explicit, [string] $EnvName, [string[]] $Candidates)
if ($Explicit) { return (Resolve-Path -LiteralPath $Explicit).Path }
$fromEnv = [Environment]::GetEnvironmentVariable($EnvName)
if ($fromEnv) { return (Resolve-Path -LiteralPath $fromEnv).Path }
foreach ($c in $Candidates) {
if (Test-Path -LiteralPath $c) { return (Resolve-Path -LiteralPath $c).Path }
}
throw @"
Could not locate $EnvName.
Run .\scripts\windows\bootstrap.ps1 to provision the dependencies, or
pass -$($EnvName.Replace('_PREFIX','Prefix')) / set `$env:$EnvName to an existing prefix.
"@
}
$deps = Join-Path $env:LOCALAPPDATA 'Luma\windows-deps'
$triplet = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64-windows-release' } else { 'x64-windows-release' }
$vcpkg = Resolve-Prefix $VcpkgPrefix 'VCPKG_PREFIX' @(
(Join-Path $deps "vcpkg\installed\$triplet"),
"C:\vcpkg\installed\$triplet",
"C:\src\vcpkg\installed\$triplet"
)
$frida = Resolve-Prefix $FridaPrefix 'FRIDA_PREFIX' @((Join-Path $deps 'frida-prefix'), 'C:\src\dist')
$r2 = Resolve-Prefix $R2Prefix 'R2_PREFIX' @((Join-Path $deps 'r2-prefix'), 'C:\src\dist')
$pharo = Resolve-Prefix $PharoPrefix 'PHARO_PREFIX' @((Join-Path $deps 'pharo-prefix'), 'C:\src\pharo')
# frida-core, radare2 and pharo-vm install .pc files with an absolute prefix
# like `prefix=C:/src/dist`. vcpkg's pkgconf trips over the colon in
# the drive letter and emits "Expected a value for variable 'prefix'",
# leaving every ${prefix}-derived variable unresolved — which in turn
# makes Swift's .systemLibrary(pkgConfig:) resolver return no include
# paths. Rewrite the prefix to a pcfiledir-relative form so the file
# is relocatable and colon-free. Idempotent.
function Repair-PkgConfigPrefix {
param([string] $PkgConfigDir)
if (-not (Test-Path $PkgConfigDir)) { return }
Get-ChildItem -Path $PkgConfigDir -Filter '*.pc' -File | ForEach-Object {
$content = Get-Content -Raw -LiteralPath $_.FullName
if ($content -match '(?m)^prefix=\$\{pcfiledir\}') { return }
$patched = $content -replace '(?m)^prefix=.*', 'prefix=${pcfiledir}/../..'
[System.IO.File]::WriteAllText($_.FullName, $patched, [System.Text.UTF8Encoding]::new($false))
}
}
Repair-PkgConfigPrefix (Join-Path $frida 'lib\pkgconfig')
Repair-PkgConfigPrefix (Join-Path $r2 'lib\pkgconfig')
Repair-PkgConfigPrefix (Join-Path $pharo 'lib\pkgconfig')
# SwiftPM emits a `-L<dir> -l<lib>` pair for every library, repeating the same
# few lib dirs hundreds of times. On the GTK/frida/r2 stack the final link
# overshoots Windows' ~32 KB command-line limit and swift build aborts with
# "The filename or extension is too long". Mirror each pkgconfig dir into a
# sibling `pkgconfig-nolibpath` with the `-L` stripped from its Libs lines, and
# put the real lib dirs on the linker's LIB search path instead. The mirror sits
# at the same depth (prefix/lib/...), so each file's `prefix=${pcfiledir}/../..`
# still resolves. The link line then carries only -l flags and fits, so no
# drive-letter subst or post-link relink is needed. Returns the mirror path.
function New-PkgConfigMirrorWithoutLibPath {
param([string] $PkgConfigDir)
if (-not (Test-Path $PkgConfigDir)) { return $null }
$mirror = Join-Path (Split-Path -Parent $PkgConfigDir) 'pkgconfig-nolibpath'
New-Item -ItemType Directory -Force -Path $mirror | Out-Null
Get-ChildItem -Path $PkgConfigDir -Filter '*.pc' -File | ForEach-Object {
$dst = Join-Path $mirror $_.Name
if ((Test-Path $dst) -and (Get-Item $dst).LastWriteTimeUtc -ge $_.LastWriteTimeUtc) { return }
$patched = Get-Content -LiteralPath $_.FullName | ForEach-Object {
# -creplace: case-sensitive, so -L (search path) is stripped but the
# -l library flags are kept.
if ($_ -match '^Libs(\.private)?\s*:') { ($_ -creplace '(^|\s)"?-L\S+', '$1').TrimEnd() } else { $_ }
}
[System.IO.File]::WriteAllLines($dst, [string[]]$patched, [System.Text.UTF8Encoding]::new($false))
}
return $mirror
}
# vcpkg ships pkgconf.exe but SwiftGtk's Package.swift invokes
# "pkg-config". Provide an alias next to pkgconf.exe.
$pkgconfTools = Join-Path $vcpkg 'tools\pkgconf'
$pkgconf = Join-Path $pkgconfTools 'pkgconf.exe'
$pkgAlias = Join-Path $pkgconfTools 'pkg-config.exe'
if ((Test-Path $pkgconf) -and -not (Test-Path $pkgAlias)) {
Copy-Item $pkgconf $pkgAlias
}
# vcpkg drops a dirent.h polyfill at $prefix/include that conflicts with
# Swift's _FoundationCShims. Mirror the root headers into a staging dir
# excluding dirent.h so clang still finds sqlite3.h, etc.
$shim = Join-Path $vcpkg 'include\vcpkg-shim'
New-Item -ItemType Directory -Force -Path $shim | Out-Null
Get-ChildItem (Join-Path $vcpkg 'include\*.h') -File | Where-Object {
$_.Name -ne 'dirent.h'
} | ForEach-Object {
$dest = Join-Path $shim $_.Name
if (-not (Test-Path $dest) -or $_.LastWriteTimeUtc -gt (Get-Item $dest).LastWriteTimeUtc) {
Copy-Item $_.FullName $dest
}
}
$pkgConfigDirs = @($frida, $r2, $pharo, $vcpkg) |
ForEach-Object { New-PkgConfigMirrorWithoutLibPath (Join-Path $_ 'lib\pkgconfig') } |
Where-Object { $_ } |
ForEach-Object { $_ -replace '\\','/' } |
Select-Object -Unique
# Root-level vcpkg headers (sqlite3.h, ...) aren't
# reachable through pkg-config, so point clang at the vcpkg-shim
# staging dir that mirrors $VCPKG_PREFIX/include minus the dirent.h
# polyfill that clashes with Swift's _FoundationCShims. Everything
# else (gtk4, libadwaita, frida-core, radare2, ...) propagates
# through SwiftPM's .systemLibrary(pkgConfig:) resolver.
$shimDir = (Join-Path $vcpkg 'include\vcpkg-shim') -replace '\\','/'
$env:VCPKG_PREFIX = $vcpkg
$env:FRIDA_PREFIX = $frida
$env:R2_PREFIX = $r2
$env:PHARO_PREFIX = $pharo
$env:PKG_CONFIG_PATH = $pkgConfigDirs -join ';'
$env:GIR_EXTRA_SEARCH_PATH = (Join-Path $vcpkg 'share\gir-1.0') -replace '\\','/'
$env:CPATH = $shimDir
$env:CPLUS_INCLUDE_PATH = $shimDir
# The pkgconfig-nolibpath mirror above drops the per-library -L; put the real
# lib dirs on the linker's LIB search path so lld-link still finds the import
# libs.
$libDirs = @($vcpkg, $frida, $r2, $pharo) |
ForEach-Object { Join-Path $_ 'lib' } |
Where-Object { Test-Path $_ } |
Select-Object -Unique
function Join-DistinctDirectories {
param([string[]] $Directories)
$seen = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
($Directories | Where-Object { $_ -and $seen.Add($_.TrimEnd('\')) }) -join ';'
}
$env:LIB = Join-DistinctDirectories (@($libDirs) + ($env:LIB -split ';'))
# Link frida-swift against the system frida-core devkit rather than
# downloading one, and point GLib at vcpkg's glib-networking so TLS
# works — luma-bundle-compiler fetches npm type packages over HTTPS,
# which fails with "TLS support is not available" without it.
$env:USE_SYSTEM_FRIDA = '1'
$env:GIO_EXTRA_MODULES = (Join-Path $vcpkg 'plugins\glib-networking') -replace '\\','/'
# gir2swift shells out to sed/awk to patch generated Swift. Git for
# Windows' cygwin build of sed (typically first on PATH for most
# Windows devs) crashes silently at fork() time on recent Windows
# versions, leaving the patches unapplied and the generated GLib
# Swift unable to compile. Any non-Git sed on disk (MSYS2, GnuWin32,
# chocolatey shim) works, so probe for one and prepend its dir.
$sedCandidates = @(
'C:\msys64\usr\bin\sed.exe',
'C:\Program Files (x86)\GnuWin32\bin\sed.exe',
'C:\ProgramData\chocolatey\bin\sed.exe'
)
$sedDir = $sedCandidates |
Where-Object { Test-Path $_ } |
Select-Object -First 1 |
ForEach-Object { Split-Path -Parent $_ }
if ($sedDir) {
$gitBefore = (Get-Command git -ErrorAction SilentlyContinue).Source
if (-not (($env:PATH -split ';') -contains $sedDir)) {
$env:PATH = "$sedDir;$env:PATH"
}
$gitAfter = (Get-Command git -ErrorAction SilentlyContinue).Source
if ($gitBefore -and $gitAfter -ne $gitBefore) {
$env:PATH = "$(Split-Path -Parent $gitBefore);$env:PATH"
}
Write-Host " sed/awk = $sedDir"
} else {
Write-Warning @"
No non-cygwin sed/awk found. gir2swift post-processing will be
skipped, and the generated GLib Swift will fail to compile.
Install one of:
winget install GnuWin32.Sed GnuWin32.Gawk
or an MSYS2 install under C:\msys64
"@
}
# Put the dependency prefixes on PATH so pkg-config and the built
# executable (via run.ps1) can find the runtime DLLs.
$prefixBins = @(
$pkgconfTools,
(Join-Path $vcpkg 'bin'),
(Join-Path $vcpkg 'tools'),
(Join-Path $frida 'bin'),
(Join-Path $r2 'bin'),
(Join-Path $pharo 'bin')
) | Where-Object { Test-Path $_ }
$missing = $prefixBins | Where-Object { ($env:PATH -split ';') -notcontains $_ }
if ($missing) {
$env:PATH = (@($missing) + $env:PATH) -join ';'
}
# rc.exe lives in the Windows SDK bin and isn't on PATH in plain
# cmd / PowerShell. Package.swift uses it to compile the exe icon
# resource when swift build runs outside a Developer prompt.
if (-not (Get-Command rc.exe -ErrorAction SilentlyContinue)) {
$sdkRoot = 'C:\Program Files (x86)\Windows Kits\10\bin'
if (Test-Path $sdkRoot) {
$sdkBin = Get-ChildItem $sdkRoot -Directory |
Where-Object { $_.Name -match '^10\.' } |
Sort-Object Name -Descending |
ForEach-Object { Join-Path $_.FullName 'x64' } |
Where-Object { Test-Path (Join-Path $_ 'rc.exe') } |
Select-Object -First 1
if ($sdkBin) { $env:PATH = "$sdkBin;$env:PATH" }
}
}
$env:PATH = Join-DistinctDirectories ($env:PATH -split ';')
Write-Host "LumaGtk build env configured:"
Write-Host " VCPKG_PREFIX = $vcpkg"
Write-Host " FRIDA_PREFIX = $frida"
Write-Host " R2_PREFIX = $r2"
Write-Host " PHARO_PREFIX = $pharo"