nushell/wix/main.wxs
Justin Ma 10be753ab7
Fix Windows arm64 release binaries and winget related issues (#15690)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
Publishing Nushell to winget has always been a challenge for us, and to
this day, many issues remain unresolved—and some seem almost impossible
to fix. The road to solving these problems may be winding and long, but
it's time for us to set out on this journey.

This PR try to fix the Windows arm64 release binaries and some `winget`
related issues:

- [x] Fixes https://github.com/nushell/nushell/issues/14815: build
Windows arm64 binaries by Windows arm64 runner
- [x] Upgrade WiX Toolset to latest 6.0 version: WiX 3 we used currently
doesn't support arm64 arch and [WiX v4 Security Fixes End Date is
2025/02/05](https://docs.firegiant.com/wix/)
- [x] Update the **nightly** workflow to make it work for all future
releases
- [x] Update the **release** workflow to make it work for all future
releases
- [x] Fixes https://github.com/nushell/nushell/issues/15698
- [x] Fixes https://github.com/nushell/nushell/issues/13719 so that
Nushell should be possible to be installed via winget with both user and
machine scope
- [x] Fixes https://github.com/nushell/nushell/issues/5927
- [x] Try to fix https://github.com/nushell/nushell/issues/14786
- [x] Fixes https://github.com/nushell/nushell/issues/9537

## Related but not planed issues:

- Related https://github.com/nushell/nushell/issues/13017
- Related https://github.com/nushell/nushell/issues/8053

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

- Nushell should be possible to be installed via winget with both user
and machine scope and The default should be user scope
  - User scope install by winget: `winget install Nushell.Nushell`
- User scope install by msiexec: `msiexec /i
nu-0.104.1-x86_64-pc-windows-msvc.msi /quiet /qn`
- Machine scope install by winget: `winget install Nushell.Nushell
--override 'ALLUSERS=1'`
- Machine scope install by msiexec: `msiexec /i
nu-0.104.1-x86_64-pc-windows-msvc.msi ALLUSERS=1`
- Note that `--scope` flag for `winget install` does not work use
`--override` instead
- Default user scope install dir:
`$'($nu.home-path)\AppData\Local\Programs\nu\'`
  - Default machine scope install dir: `C:\Program Files\nu\`
- When a standard user runs the installer and selects "Install for
everyone" (per-machine installation), Windows will automatically trigger
a UAC prompt, the user can enter admin credentials and the installation
will proceed with elevated privileges
- [hustcer/setup-nu](https://github.com/hustcer/setup-nu) should work
for `windows-11-arm` runners


# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

The latest MSI builds are available here:
https://github.com/nushell/nightly/releases/tag/v0.104.1
Actually all the nightly releases were built with latest changes
included: https://github.com/nushell/nightly/releases

`winget` and `msiexec` install tests goes here:
https://github.com/nushell/integrations/pull/49

https://github.com/nushell/integrations/actions/runs/14974621061
https://github.com/nushell/integrations/actions/runs/14974621054

### Test winget install locally:
- git clone git@github.com:nushell/integrations.git
- User scope install: `winget install -m
manifests\n\Nushell\Nushell\0.104.1\`
- Run: `use tests\common.nu *; check-user-install`
- Machine scope install: `winget install -m
manifests\n\Nushell\Nushell\0.104.1\ --override 'ALLUSERS=1'`
- Run: `use tests\common.nu *; check-local-machine-install`

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

@fdncred I suggest releasing a patch version after merging this PR (only
the changes of this PR will be included) to ensure that the winget
release process works properly. This way, we can be more confident when
releasing version 0.105.0.

References:

-
https://learn.microsoft.com/en-us/windows/win32/msi/single-package-authoring
-
https://learn.microsoft.com/en-us/windows/package-manager/winget/source#add
-
https://github.com/microsoft/winget-pkgs/blob/master/doc/tools/SandboxTest.md
- https://docs.firegiant.com/quick-start/
-
https://docs.firegiant.com/wix3/tutorial/getting-started/putting-it-to-use/#_top
-
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec#set-public-properties
2025-05-22 19:15:52 +08:00

298 lines
14 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<!--
- https://learn.microsoft.com/en-us/windows/win32/msi/single-package-authoring
- https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
<?define ProductName = "Nushell" ?>
<?define ApplicationFolderName = "nu" ?>
<?define ProductVersion = "$(env.NU_RELEASE_VERSION)" ?>
<?define Manufacturer = "The Nushell Project Developers" ?>
<?define UpgradeCode = "82D756D2-19FA-4F09-B10F-64942E89F364" ?>
<!-- https://docs.firegiant.com/wix/schema/wxs/package/ -->
<Package
Compressed="yes"
Id="Nushell.Nushell"
InstallerVersion="500"
Scope="perUserOrMachine"
Name="$(var.ProductName)"
Version="$(var.ProductVersion)"
UpgradeCode="$(var.UpgradeCode)"
Manufacturer="$(var.Manufacturer)" >
<MajorUpgrade
MigrateFeatures="yes"
Schedule="afterInstallInitialize"
DowngradeErrorMessage="A newer version of [ProductName] is already installed. Setup will now exit." />
<!-- Embed cab media to MSI file -->
<Media Id="1" Cabinet="cab1.cab" EmbedCab="yes" />
<!-- Allow install for Current User Or Machine -->
<Property Id="WixUISupportPerUser" Value="1" />
<Property Id="WixUISupportPerMachine" Value="1" />
<!-- Install for PerUser by default -->
<!-- If set to WixPerMachineFolder will install for PerMachine by default -->
<Property Id="WixAppFolder" Value="WixLocalAppDataFolder" />
<!--
Support for winget install `——scope machine`:
Winget doesn't directly pass ALLUSERS=1 to the MSI installer when using
——scope machine. Instead, it passes a property called INSTALLSCOPEMACHINE=1
This converts the winget parameter to the standard MSI parameter.
FIXME: However, this doesn't seem to work...
-->
<SetProperty Id="ALLUSERS" Value="1" After="LaunchConditions" Condition="INSTALLSCOPEMACHINE=1" />
<!-- This ensures the per-user flag is turned off when installing for all users. -->
<SetProperty Id="MSIINSTALLPERUSER" Value="0" After="LaunchConditions" Condition="INSTALLSCOPEMACHINE=1" />
<!-- Workaround Wix Bug: https://github.com/wixtoolset/issues/issues/2165 -->
<!-- The suggested folder in the dest folder dialog should be C:\Program Files\nu -->
<CustomAction Id="Overwrite_WixSetDefaultPerMachineFolder" Property="WixPerMachineFolder"
Value="[ProgramFiles64Folder][ApplicationFolderName]" Execute="immediate" />
<CustomAction Id="Overwrite_ARPINSTALLLOCATION" Property="ARPINSTALLLOCATION"
Value="[ProgramFiles64Folder][ApplicationFolderName]" Execute="immediate" />
<InstallUISequence>
<Custom Action="Overwrite_WixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="Overwrite_WixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
<Custom Action="Overwrite_ARPINSTALLLOCATION" After="InstallValidate"/>
</InstallExecuteSequence>
<!-- Enable UAC prompt when installing for all users -->
<!-- <Property Id="MSIUSEREALADMINDETECTION" Value="1" /> -->
<Icon Id="ProductIconId" SourceFile="$(var.ProjectDir)/nu.ico"/>
<Property Id="ARPPRODUCTICON" Value="ProductIconId" />
<Property Id='ARPHELPLINK' Value='https://www.nushell.sh/book/' />
<Property Id="ApplicationFolderName" Value="$(var.ApplicationFolderName)" />
<!-- INSTALLDIR is the logical target directory whose path will be set by the INSTALLDIR property. -->
<Directory Id="INSTALLDIR" Name="$(var.ApplicationFolderName)">
<!-- LOGICAL_BINDIR's path will be set by the LOGICAL_BINDIR property -->
<Directory Id="LOGICAL_BINDIR" Name="bin" />
</Directory>
<!-- Per Machine Install - these are the definitions of the physical locations -->
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="APPLICATIONFOLDER" Name="$(var.ApplicationFolderName)">
<Directory Id="BINDIR" Name="bin">
<!-- Per-machine PATH component -->
<Component Id="EnvironmentPathMachine" Guid="*" Condition="ALLUSERS=1 AND NOT MSIINSTALLPERUSER=1">
<!-- The value MUST BE [LOGICAL_BINDIR] to make sure the env been removed for a custom dir installation -->
<Environment Id="PATHMachine"
Name="PATH"
Value="[LOGICAL_BINDIR]"
Permanent="no"
Part="last"
Action="set"
System="yes" />
<RegistryValue Root="HKLM"
Key="Software\nu"
Name="installed"
Type="integer"
Value="1"
KeyPath="yes" />
</Component>
</Directory>
</Directory>
</StandardDirectory>
<!-- Install for Current User - these are the definitions of the physical locations -->
<StandardDirectory Id="LocalAppDataFolder">
<Directory Id="LocalAppProgramsFolder" Name="Programs">
<Directory Id="INSTALLDIR_USER" Name="$(var.ApplicationFolderName)">
<Directory Id="BINDIR_USER" Name="bin">
<!-- Per-user PATH component -->
<Component Id="EnvironmentPathUser" Guid="*" Condition="MSIINSTALLPERUSER=1">
<Environment Id="PATHUser"
Name="PATH"
Value="[BINDIR_USER]"
Permanent="no"
Part="last"
Action="set"
System="no" />
<RegistryValue Root="HKCU"
Key="Software\nu"
Name="installed"
Type="integer"
Value="1"
KeyPath="yes" />
</Component>
</Directory>
</Directory>
</Directory>
<!-- Windows Terminal Profile Directories -->
<Directory Id="AppDataMicrosoftFolder" Name="Microsoft">
<Directory Id="AppDataWindowsTerminalFolder" Name="Windows Terminal">
<Directory Id="WindowsTerminalProfileFolder" Name="Fragments">
<Directory Id="WindowsTerminalProfileAppFolder" Name="$(var.ApplicationFolderName)">
<Component Id="WindowsTerminalProfile" Guid="*">
<File Id="WindowsTerminalProfileFile"
Name="nu.json"
KeyPath="yes"
Source="$(var.ProjectDir)\windows-terminal-profile.json" />
<RegistryValue Root="HKCU"
Key="Software\nu"
Name="WindowsTerminalProfile"
Value="1"
Type="integer" />
<RemoveFolder Id="RemoveWindowsTerminalProfileFolderA" Directory="WindowsTerminalProfileAppFolder" On="uninstall" />
<RemoveFolder Id="RemoveWindowsTerminalProfileFolderB" Directory="WindowsTerminalProfileFolder" On="uninstall" />
<RemoveFolder Id="RemoveWindowsTerminalProfileFolderC" Directory="AppDataWindowsTerminalFolder" On="uninstall" />
<RemoveFolder Id="RemoveWindowsTerminalProfileFolderD" Directory="AppDataMicrosoftFolder" On="uninstall" />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</StandardDirectory>
<ComponentGroup Id="NushellBinaries" Directory="LOGICAL_BINDIR">
<Component Id="Nu_Main" Guid="*">
<File Id="nu.exe" Source="$(var.SourceDir)\nu.exe" KeyPath="yes" />
</Component>
<Component Id="Less" Guid="*">
<File Id="less.exe" Source="$(var.SourceDir)\less.exe" KeyPath="yes" />
</Component>
<Component Id="Nu_Plugin_Inc" Guid="*">
<File Id="nu_plugin_inc.exe" Source="$(var.SourceDir)\nu_plugin_inc.exe" KeyPath="yes" />
</Component>
<Component Id="Nu_Plugin_Gstat" Guid="*">
<File Id="nu_plugin_gstat.exe" Source="$(var.SourceDir)\nu_plugin_gstat.exe" KeyPath="yes" />
</Component>
<Component Id="Nu_Plugin_Query" Guid="*">
<File Id="nu_plugin_query.exe" Source="$(var.SourceDir)\nu_plugin_query.exe" KeyPath="yes" />
</Component>
<Component Id="Nu_Plugin_Polars" Guid="*">
<File Id="nu_plugin_polars.exe" Source="$(var.SourceDir)\nu_plugin_polars.exe" KeyPath="yes" />
</Component>
<Component Id="Nu_Plugin_Formats" Guid="*">
<File Id="nu_plugin_formats.exe" Source="$(var.SourceDir)\nu_plugin_formats.exe" KeyPath="yes" />
</Component>
<Component Id="Less_License" Guid="*">
<File Id="LICENSE_for_less.txt" Source="$(var.SourceDir)\LICENSE-for-less.txt" KeyPath="yes" />
</Component>
</ComponentGroup>
<!-- License and Icon in main installation directory -->
<ComponentGroup Id="NushellResources" Directory="INSTALLDIR">
<Component Id="Nu_Icon" Guid="*">
<File Id="nu.ico" Source="$(var.ProjectDir)\nu.ico" KeyPath="yes" />
</Component>
<Component Id="Nu_Readme" Guid="*">
<File Id="README.txt" Source="$(var.ProjectDir)\README.txt" KeyPath="yes" />
</Component>
<Component Id="Nu_License" Guid="*">
<File Id="License.rtf" Source="$(var.ProjectDir)\License.rtf" KeyPath="yes" />
</Component>
</ComponentGroup>
<!-- Main feature set -->
<Feature Id="ProductFeature"
Level="1"
Title="Nushell"
Description="Install $(var.ProductName) and plugins.">
<ComponentGroupRef Id="NushellBinaries" />
<ComponentGroupRef Id="NushellResources" />
<ComponentRef Id="EnvironmentPathUser" />
<ComponentRef Id="EnvironmentPathMachine" />
</Feature>
<!-- Windows Terminal Profile Feature -->
<Feature Id="WindowsTerminalProfileFeature"
Level="1"
Title="Windows Terminal Profile"
Description="Add $(var.ProductName) profile to Windows Terminal.">
<ComponentRef Id="WindowsTerminalProfile" />
</Feature>
<!-- Load Advanced UI -->
<WixVariable Id="WixUILicenseRtf" Value="$(var.ProjectDir)\License.rtf" />
<ui:WixUI Id="WixUI_Advanced" />
<!-- Windows Version Check -->
<Launch Condition="VersionNT >= 601" Message="This application requires Windows 7 or later." />
<!-- Arch checking -->
<?if $(sys.BUILDARCH) = x64 ?>
<Launch Condition="VersionNT64" Message="This installation package is only supported on 64-bit Windows." />
<?endif?>
<?if $(sys.BUILDARCH) = arm64 ?>
<Launch Condition="ProcessorArchitecture = 'ARM64'" Message="This installation package is only supported on ARM64 Windows." />
<?endif?>
<!-- If installing per-user (MSIINSTALLPERUSER=1), this sets INSTALLDIR and BINDIR to point to user-specific paths -->
<SetProperty Id="INSTALLDIR"
Action="SetINSTALLDIR_User"
Value="[LocalAppDataFolder]Programs\$(var.ApplicationFolderName)"
After="LaunchConditions"
Condition="MSIINSTALLPERUSER=1"
Sequence="both" />
<SetProperty Id="BINDIR"
Action="SetBINDIR_User"
Value="[LocalAppDataFolder]Programs\$(var.ApplicationFolderName)\bin"
After="LaunchConditions"
Condition="MSIINSTALLPERUSER=1"
Sequence="both" />
<!-- If installing per-machine (ALLUSERS=1 AND NOT MSIINSTALLPERUSER=1), this sets INSTALLDIR and BINDIR to point to machine-wide (Program Files) paths -->
<SetProperty Id="INSTALLDIR"
Action="SetINSTALLDIR_Machine"
Value="[APPLICATIONFOLDER]"
After="LaunchConditions"
Condition="ALLUSERS=1 AND NOT MSIINSTALLPERUSER=1"
Sequence="both" />
<!-- Override APPLICATIONFOLDER for 64-bit installations -->
<?if $(sys.BUILDARCH) = x64 OR $(sys.BUILDARCH) = arm64 ?>
<SetProperty Id="APPLICATIONFOLDER"
Value="[ProgramFiles64Folder]$(var.ApplicationFolderName)"
After="LaunchConditions"
Condition="ALLUSERS=1 AND NOT MSIINSTALLPERUSER=1"
Sequence="both" />
<?endif?>
<SetProperty Id="BINDIR"
Action="SetBINDIR_Machine"
Value="[BINDIR]"
After="LaunchConditions"
Condition="ALLUSERS=1 AND NOT MSIINSTALLPERUSER=1"
Sequence="both" />
<!-- Set the LOGICAL_BINDIR property to the resolved BINDIR path -->
<!-- This line MUST NOT be removed in order to set a custom folder for PerMachine installation -->
<SetProperty Id="LOGICAL_BINDIR" Value="[BINDIR]" After="LaunchConditions" Sequence="both" />
<!-- Property that defines the command executed by the Windows Terminal Profile custom action -->
<!-- for Value, see https://learn.microsoft.com/en-ca/windows/win32/msi/formatted -->
<SetProperty Id="ReplacePathsInWindowsTerminalProfile"
Sequence="execute"
After="CostFinalize"
Value="&quot;[#nu.exe]&quot; -c &quot;let doc = (open `[#WindowsTerminalProfileFile]` | update profiles.commandline `[#nu.exe]` | update profiles.icon `[#nu.ico]`); $doc | save -f `[#WindowsTerminalProfileFile]`&quot;"
Condition="&amp;WindowsTerminalProfileFeature=3" />
<!-- Defines the custom action that updates paths in the Windows Terminal profile JSON file -->
<CustomAction Id="ReplacePathsInWindowsTerminalProfile"
Return="check"
Impersonate="yes"
Execute="deferred"
DllEntry="WixQuietExec"
BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
<InstallExecuteSequence>
<Custom Action="ReplacePathsInWindowsTerminalProfile" Before="InstallFinalize"
Condition="&amp;WindowsTerminalProfileFeature=3" />
</InstallExecuteSequence>
</Package>
</Wix>