diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 46debde..19bfc2d 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -63,9 +63,22 @@ jobs: sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install -y jq wine locales - # C.UTF-8 is glibc >=2.35 only (Debian 12+); en_US.UTF-8 works on any - # Debian version and just needs generating once. - sudo locale-gen en_US.UTF-8 + # makensis converts its argv through the locale charset and aborts with + # "FATAL: main argv conversion failed!" under C/POSIX. C.UTF-8 is a glibc + # built-in only from 2.35 (Debian 12+), so en_US.UTF-8 is generated instead. + # + # `locale-gen ` is an Ubuntu extension: plain Debian's locale-gen + # takes no arguments and only reads /etc/locale.gen, so the entry goes in + # there first. The anchored grep does not match the line the package ships + # commented out. + grep -q '^en_US.UTF-8 UTF-8' /etc/locale.gen \ + || echo 'en_US.UTF-8 UTF-8' | sudo tee -a /etc/locale.gen + sudo locale-gen + locale -a + locale -a | grep -qiE '^en_US\.utf-?8$' || { + echo "en_US.UTF-8 was not generated; makensis will abort on argv conversion" >&2 + exit 1 + } - name: Ensure PowerShell # Debian ships no snapd by default, so this pulls Microsoft's portable @@ -82,6 +95,25 @@ jobs: sudo ln -sf /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh sudo chmod +x /opt/microsoft/powershell/7/pwsh + - name: makensis locale probe + # Diagnostic only, never fails the job. If `-VERSION` prints a version under + # en_US.UTF-8 and "FATAL: main argv conversion failed!" under C, the locale + # really is the whole story. If it fails under both, it is not the locale and + # the next thing to try is DEBUG=electron-builder on the build step to see the + # argv makensis is actually handed. Prints nothing useful on the very first + # run of a fresh runner, where the NSIS bundle has not been downloaded yet. + continue-on-error: true + run: | + locale || true + mk=$(find "$HOME/.cache/electron-builder" /var/lib/gitea-runner/.cache/electron-builder \ + -type f -name makensis -path '*/linux/*' 2>/dev/null | head -n1) + echo "makensis: ${mk:-}" + [ -n "$mk" ] || exit 0 + echo "--- LC_ALL=en_US.UTF-8" + LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 "$mk" -VERSION || echo "failed, exit $?" + echo "--- LC_ALL=C" + LC_ALL=C LANG=C "$mk" -VERSION || echo "failed, exit $?" + - name: Build Windows installers + Linux AppImage shell: pwsh env: diff --git a/scripts/build-release.ps1 b/scripts/build-release.ps1 index 2359802..838e890 100644 --- a/scripts/build-release.ps1 +++ b/scripts/build-release.ps1 @@ -169,6 +169,54 @@ function Invoke-Checked { } } +function Set-Utf8Locale { + <# + electron-builder's native Linux makensis converts its own argv through the + locale charset before it parses anything, and cannot do it under a non-UTF-8 + locale: + + ⨯ .../nsis-3.0.4.1/linux/makensis process failed ERR_ELECTRON_BUILDER_CANNOT_EXECUTE + Exit code: 84 + Output: FATAL: main argv conversion failed! + + A minimal Debian box — a CI runner in particular — defaults to C/POSIX, + which is exactly that case. Setting it here rather than only in the CI + workflow's env: block means the electron-builder child inherits it however + this script was started: a runner that drops step env, a bare + `pwsh ./scripts/build-release.ps1` over ssh, cron. + + Which locales exist is a property of the host, so `locale -a` is consulted + rather than guessed at: C.UTF-8 is a glibc built-in only from 2.35 + (Debian 12+), and en_US.UTF-8 exists only once someone has run locale-gen. + #> + $current = if ($env:LC_ALL) { $env:LC_ALL } else { $env:LANG } + if ($current -match '(?i)\.utf-?8$') { return } + + $available = @() + try { $available = @(& locale -a 2>$null) } catch { } + + # Debian spells them "C.utf8" / "en_US.utf8" in `locale -a` but wants + # "en_US.UTF-8 UTF-8" in /etc/locale.gen; both spellings are accepted by + # setlocale, so match either and fall back to any UTF-8 locale at all. + $pick = @("C.UTF-8", "C.utf8", "en_US.UTF-8", "en_US.utf8") | + Where-Object { $available -contains $_ } | Select-Object -First 1 + if (-not $pick) { + $pick = $available | Where-Object { $_ -match '(?i)\.utf-?8$' } | Select-Object -First 1 + } + if (-not $pick) { + throw ("no UTF-8 locale on this host, and makensis aborts with 'FATAL: main argv " + + "conversion failed!' without one. Generate one:`n" + + " sudo apt-get install -y locales`n" + + " echo 'en_US.UTF-8 UTF-8' | sudo tee -a /etc/locale.gen`n" + + " sudo locale-gen`n" + + "then re-run. 'locale -a' listed: $($available -join ', ')") + } + + Write-Host " locale: LC_ALL=$pick (was '$current'; makensis needs UTF-8)" -ForegroundColor DarkGray + $env:LC_ALL = $pick + $env:LANG = $pick +} + function Get-ArtifactName { <# electron-builder rejects an artifactName that has no ${ext} macro, and @@ -562,6 +610,14 @@ try { "mkdir -p $(ConvertTo-BashArg $wslStage) && rm -f $(ConvertTo-BashArg $wslStage)/$prefix-*") } + # Both Windows targets go through makensis (portable is an NSIS build too), and + # on a Linux host that is a native binary this process's locale reaches. The WSL + # path is not covered: wsl.exe does not carry $env:LC_ALL into the distro, and a + # distro someone installed interactively has a UTF-8 locale already. + if (-not $onWindows -and @($resolvedTargets | Where-Object { $_ -like "win*" }).Count) { + Set-Utf8Locale + } + foreach ($target in $resolvedTargets) { Write-Host "Packaging $target..." -ForegroundColor Cyan