Portable launchers have a separate rule and runtime contract.
electron_nsis.license accepts a declared UTF-8 .txt or .rtf agreement.
Interactive installation shows it before directory selection and requires a
checked acceptance box. Text normalizes to BOM-marked UTF-16LE with CRLF;
RTF bytes stay unchanged. Inputs must be nonempty and at most 2 MiB. License
paths/content do not enter script expressions. Silent /S installs and
electron-updater skip interactive pages, following NSIS behavior.
licenses maps declared files to locales, with license_default_language as
fallback. It is mutually exclusive with license. Multiple locales show NSIS's
language selector before consent; /S skips the selector too. Language tables
and LangDLL come from the declared tool tree, with no host discovery. Supported
locales are listed in LICENSE_LANGUAGES in the packager. Inputs have a 16 MiB
combined normalized limit. The implementation follows
NSIS multilingual licenses.
The example declares English text and Japanese RTF agreements for both scopes.
The 6051414 native workflow
passed all four hosts, including English text/Japanese RTF selection, Unicode
consent, cancellation and both Windows installer scopes. BB compilation
checks installer/script reproducibility with relocated license inputs. Native
Windows smoke checks visible text, disabled Next before acceptance, enabled Next
after checking, and cancellation without registration. The
6d7b3d6 suite,
seven cross-builds
and all four native hosts
passed. Both Windows CPUs verified interactive per-user consent/cancellation,
then real per-user and machine update/uninstall lifecycles. The Unicode fixture
also passed native consent/cancellation on both Windows CPUs at c963d16
(workflow),
including exact CJK and supplementary-plane text. That workflow's macOS job was
still running when recorded. Multilingual selection passed the
9d4684f BB suite,
seven cross-builds
and both Windows CPUs in native smoke.
Each selected English and Japanese, checked the matching visible license and
Unicode text, required acceptance, then canceled before installation. Per-user
and machine install/update/uninstall also passed. The macOS consent gate in
that workflow failed separately. Selector cancellation now sets user-cancel exit
status 1 explicitly. Both Windows CPUs verified that status in the
4151ef3 native workflow,
which passed all four hosts, including the separate macOS gate.
The NSIS script output group uses portable app/, icon and payload.7z
references. Worker paths stay inside compilation. verify_reproducible compares
both installer and exported script bytes across relocated staging directories.
Icon inputs relocate too; shared or app-contained icons retain their logical
roles in the exported script. The fdaf424 BB suite
and seven cross-builds
passed. Both Windows CPU jobs also passed installer/update/removal in the
native workflow;
that workflow's macOS job failed the separate Finder background getter check.
The per-user uninstall smoke waits for the default uninstaller's complete process tree before checking owned files, shortcuts and registrations. NSIS copies its uninstaller to a temporary child; waiting only for the launcher can observe partial cleanup. Shortcut/ownership assertions remain required. This follows NSIS's documented self-copy behavior and PowerShell's process-tree wait. The 3fe910c BB suite, seven cross-builds and all four native hosts passed, including per-user uninstall on both Windows CPUs.
@rules_electron//electron/tools:windows_elevate builds a native elevation bridge
for the selected Windows CPU/compiler. Map it to resources/elevate.exe for the
pinned electron-updater NSIS elevation contract. It accepts an absolute executable
path followed by arguments and invokes the Windows runas verb. Default launch
exits after process creation, releasing the helper image before an updater
replaces the installed app. An initial --wait waits and returns the child exit
code for explicit release/test commands. Unicode, empty arguments, quotes and
trailing backslashes
are preserved without a shell interpreter. The helper uses the Windows GUI
subsystem, so updater launch does not open a console window.
Compilation runs on BB Linux. Matching Windows smoke checks the native PE CPU, elevated child token, exact arguments, exit status, detached launch without an installer-file lock, and invalid-input failures for both compiler variants. Already elevated disposable runners exercise the bridge without an interactive UAC prompt. Interactive consent/cancellation remains unverified. The machine lifecycle gate validates installer/update behavior separately.
Set electron_nsis.installation_scope = "machine" for an elevated all-users
installer. It defaults to $PROGRAMFILES64/<app_id>, uses 64-bit HKLM registration
and common Start Menu/desktop shortcuts. User scope remains the default, using
HKCU and LocalAppData. Both scopes write an ownership scope marker; installers
and uninstallers reject a mismatched scope. Existing user installs without that
new marker retain their user-scope upgrade path.
Machine installers require empty updater_cache_name: an elevated account can
differ from the app user, so installation does not seed that account's private
cache. Publish their filenames in electron_update_feed.admin_rights_required
and package the bridge as resources/elevate.exe. The real client then uses its
elevation branch. An uncached first machine update downloads the complete
installer; differential cache seeding across users remains open.
The 60fe44f BB suite and cross-build passed. The native gate passed all four hosts. Windows x64/ARM64 checked both elevation bridge compiler variants and the MSVC bootstrap Electron machine lifecycle: Program Files/HKLM, common shortcuts, unchanged HKCU state, corrupt-update rejection, real elevated v1-to-v2 update, scope rejection, removal and preserved foreign handlers/data. Production signatures, interactive UAC and full source-runtime machine installs remain separate gates.
The bridge preserves raw /S and --updated switches. NSIS's
command-line contract
requires /D last and unquoted; the generated installer also parses the updater's
directory option explicitly. The scope-rejection probe uses _?= to run the
actual uninstaller in place and observe its exit status, rather than its
short-lived launcher.
installer_icon and uninstaller_icon accept declared ICO labels, including
generated icons. NSIS embeds them independently using its
Icon and UninstallIcon attributes.
Unspecified icons retain NSIS defaults. Invalid ICO containers fail before
compilation. App executable branding remains electron_app.windows_icon.
The native fixture uses different app, installer and uninstaller colors, then extracts their executable icons through Win32. Installer reproducibility and installed update checks also include these icons. BB tests, seven cross-builds and all four native hosts passed, including distinct installer/uninstaller icons on both Windows CPUs/compiler variants.
Shortcuts receive app_user_model_id, defaulting to electron_app.app_id.
The value allows at most 128 UTF-16 units and no whitespace. Both Start Menu
and optional desktop shortcuts receive it on each installation or upgrade.
The declared NSIS data tree must include the Unicode WinShell plugin; the
example's integrity-pinned bundle already provides it.
Set the same ID early in the Electron main process:
if (process.platform === 'win32') app.setAppUserModelId('org.example.sample-desktop');See Electron's runtime API and Windows shortcut identity. Packaging sets shortcut properties; app code owns process identity. Native smoke reads shortcut properties through Electron and process identity through Win32, before and after installed updates. BB tests, seven cross-builds and all native hosts passed; both Windows compiler/CPU variants checked shortcut and process identity.
electron_nsis builds a per-user installer and uninstaller from a Windows
electron_app. The compiler runs on the execution platform; payload binaries
never execute during packaging.
load("@rules_electron//electron:nsis.bzl", "electron_nsis", "electron_nsis_tool")
electron_nsis_tool(
name = "nsis_tool",
compiler = ":execution_makensis",
archive_compiler = ":execution_7zip",
data = ":declared_nsisdir",
)
electron_nsis(
name = "setup",
app = ":app",
tool = ":nsis_tool",
protocols = ["example-desktop"],
updater_cache_name = "example-desktop-updater",
)The data tree contains NSIS Include, Stubs, Plugins and Contrib directories.
The example pins NSIS 3.13 from the public tool bundle by SHA-256. It invokes the
Linux x64 compiler directly with an explicit NSISDIR and -NOCONFIG; no wrapper,
host installation, package manager, Wine or electron-builder runs in build
actions. Compiler, stubs and plugins are bootstrap tools, not source builds in
this repository. A source-built compiler can use the same tool provider.
Default 7z compression uses independent per-file LZMA2 streams, a 1 MiB
dictionary, two fixed encoder threads and no stored timestamps. NSIS stores the
archive without recompressing it and extracts through the declared plugin.
Automatic architecture filters are disabled: the extractor predates newer ARM64
filters. Only plain LZMA2/Copy streams are accepted; extracted file sizes are
checked before success.
compression also accepts solid zlib, bzip2, lzma and off; the direct
NSIS LZMA mode uses an 8 MiB dictionary. verify_reproducible = True compiles a
second installer using a copied input tree with changed mtimes and different
source/output paths, then requires byte equality. The example enables this on
v1 installers for both CPUs. No clock value enters generated scripts; file date
saving is disabled. Inspect generated source with output group script.
Installation uses HKCU, current-user shortcuts and a default directory below
Local AppData Programs. A custom /D directory remains registered for updates.
The installer checks payload PE architecture and native Windows CPU separately.
NSIS's bootstrap stub is x86, including for ARM64 payloads. Windows ARM64 runs
that stub through OS compatibility; the installed app/helper/addon are native
ARM64 and tested on a native ARM64 host.
Installers refuse nonempty unowned directories, foreign ownership markers and foreign URI scheme registrations. Upgrades wait for app processes to exit before running the owned old uninstaller and replacing its payload. Uninstall removes the compiled list of owned files and empty directories, not a recursive install root. User-created files remain. It removes protocol keys only when their owner and launch command still match. App data and updater caches remain after uninstall. This format does not provide transactional rollback.
Set updater_cache_name to the same value as the generated app-update.yml.
Installation seeds the current installer as installer.exe for
electron-updater differential downloads. --updated, /S, --force-run and
custom install directories support the updater's installer invocation. Generate
channel manifests and external blockmaps from final installers with
electron_update_feed; signing must finish before those hashes are calculated.
Current scope: user/machine installation, Start Menu/optional desktop shortcuts,
AppUserModelID, URI schemes, Open With/Default Apps handlers and declared icons.
Production elevation/consent policy, custom UI and release Authenticode remain
pending. Paths must be valid Windows names; NSIS
preprocessor expressions (${...} / $%...) and control characters in literals
are rejected.
Native smoke uses real installers and NsisUpdater: refuse an unowned directory
and foreign protocol, install v1 to a path with spaces, inspect registration,
launch, reject corrupted bytes, download v2 through HTTP ranges, install/launch
v2, then uninstall while preserving a user-created file. It requires actual
differential reuse, not a full-download fallback. Hosted results establish
whether this contract passes.
Initial x64/ARM64 lifecycle validation passed install, hash rejection, upgrade, v2 launch and uninstall. Fixed-size maps reused only 64 KiB of roughly 125 MB installers. Content-defined maps alone still downloaded roughly 97%: the solid compressed stream also changed throughout. With independent LZMA2 payload streams and content-defined maps, 39cda07 native validation passed on both CPUs. x64 downloaded 776,681 of 96,256,700 bytes; ARM64 downloaded 760,466 of 89,390,353 bytes. Each reused over 99% for this sample's version-only change. The gate requires less than 50% over ranges, with no full-download fallback. Application changes can produce different reuse ratios.
file_association_icons maps registered extensions to app-relative ICO paths:
file_associations = {".example": "Example Document"},
file_association_icons = {".example": "resources/document.ico"},Include each icon through the app's declared resource/file mappings. Installer
validation requires a normalized relative path, a regular payload file and a
valid ICO container. Unspecified associations use the app executable icon.
Payload ownership covers icon upgrade/removal. User defaults remain unchanged.
Native shell resolution through
ASSOCSTR_DEFAULTICON
and Win32 image decoding passed before/after installed updates on
Windows x64
and Windows ARM64
for a3f7142. Both hosts also passed uninstall ownership checks.
An ARM64 run at a414167 downloaded the update but stayed at v1 after 120 seconds. Its failure snapshot showed a pending installer and no remaining app/installer processes. The next scheduled run passed the full lifecycle. The timeout remains unexplained. Native smoke now subscribes to Windows process start/stop events around the real updater launch; it records only fixed fixture names and numeric exit codes. Success requires observed installer start and zero exit status in addition to installed v2. This observer uses existing Windows APIs; no code is compiled on the smoke host. The first observer run installed v2 on both CPUs but missed installer exit events when filtering stop notifications by image name. It now correlates stop events with observed fixture starts by PID; IDs and timestamps stay inside the observer. Native Windows x64 and ARM64 passed actual installer start, exit code zero and the complete lifecycle. Successful repeat validation does not close the original intermittent failure.
The da21d47 x64 job and 24ca713 x64 job installed machine v2 but missed its installer start/exit events. The observer now receives unfiltered start notifications, whitelists names locally and resolves a live process's module basename when its provider name differs. Only fixture names and numeric exit codes leave the observer; PID/time/path values stay internal. Success still requires start plus zero exit status. The 02da44f native matrix passed actual installer starts and zero exits for user/machine updates on both Windows CPUs, plus install/update/remove behavior. No name recovery was needed in that observation; the precise provider failure remains unproven.