From 10be753ab746b38a7778f7f416eb91c5072cb507 Mon Sep 17 00:00:00 2001 From: Justin Ma Date: Thu, 22 May 2025 19:15:52 +0800 Subject: [PATCH] Fix Windows arm64 release binaries and winget related issues (#15690) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description 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 - 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 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 @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 --- .github/workflows/nightly-build.yml | 148 ++++-- .github/workflows/release-pkg.nu | 79 ++-- .github/workflows/release.yml | 44 +- scripts/build.rs | 2 +- wix/License.rtf | Bin 1288 -> 1289 bytes wix/main.wixproj | 36 ++ wix/main.wxs | 699 +++++++++++----------------- wix/nu.ico | Bin 0 -> 7488 bytes 8 files changed, 489 insertions(+), 519 deletions(-) create mode 100644 wix/main.wixproj create mode 100644 wix/nu.ico diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 8da8cf9981..551579ea03 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -4,17 +4,18 @@ # 2. https://github.com/JasonEtco/create-an-issue # 3. https://docs.github.com/en/actions/learn-github-actions/variables # 4. https://github.com/actions/github-script +# 5. https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds # name: Nightly Build on: - workflow_dispatch: push: branches: - nightly # Just for test purpose only with the nightly repo # This schedule will run only from the default branch schedule: - cron: '15 0 * * *' # run at 00:15 AM UTC + workflow_dispatch: defaults: run: @@ -26,6 +27,11 @@ jobs: runs-on: ubuntu-latest # This job is required by the release job, so we should make it run both from Nushell repo and nightly repo # if: github.repository == 'nushell/nightly' + # Map a step output to a job output + outputs: + skip: ${{ steps.vars.outputs.skip }} + build_date: ${{ steps.vars.outputs.build_date }} + nightly_tag: ${{ steps.vars.outputs.nightly_tag }} steps: - name: Checkout uses: actions/checkout@v4 @@ -58,16 +64,53 @@ jobs: # All the changes will be overwritten by the upstream main branch git reset --hard src/main git push origin main -f - let sha_short = (git rev-parse --short origin/main | str trim | str substring 0..7) - let tag_name = $'nightly-($sha_short)' - if (git ls-remote --tags origin $tag_name | is-empty) { - git tag -a $tag_name -m $'Nightly build from ($sha_short)' + + - name: Create Tag and Output Tag Name + if: github.repository == 'nushell/nightly' + id: vars + shell: nu {0} + run: | + let date = date now | format date %m%d + let version = open Cargo.toml | get package.version + let sha_short = (git rev-parse --short origin/main | str trim | str substring 0..6) + let latest_meta = http get https://api.github.com/repos/nushell/nightly/releases + | sort-by -r created_at + | where tag_name =~ nightly + | get tag_name?.0? | default '' + | parse '{version}-nightly.{build}+{hash}' + if ($latest_meta.0?.hash? | default '') == $sha_short { + print $'(ansi g)Latest nightly build is up-to-date, skip rebuilding.(ansi reset)' + $'skip=true(char nl)' o>> $env.GITHUB_OUTPUT + exit 0 + } + let prev_ver = $latest_meta.0?.version? | default '0.0.0' + let build = if ($latest_meta | is-empty) or ($version != $prev_ver) { 1 } else { + ($latest_meta | get build?.0? | default 0 | into int) + 1 + } + let nightly_tag = $'($version)-nightly.($build)+($sha_short)' + $'build_date=($date)(char nl)' o>> $env.GITHUB_OUTPUT + $'nightly_tag=($nightly_tag)(char nl)' o>> $env.GITHUB_OUTPUT + if (git ls-remote --tags origin $nightly_tag | is-empty) { + ls **/Cargo.toml | each {|file| + open --raw $file.name + | str replace --all $'version = "($version)"' $'version = "($version)-nightly.($build)"' + | save --force $file.name + } + # Disable the following two workflows for the automatic committed changes + rm .github/workflows/ci.yml + rm .github/workflows/audit.yml + + git add . + git commit -m $'Update version to ($version)-nightly.($build)' + git tag -a $nightly_tag -m $'Nightly build from ($sha_short)' git push origin --tags + git push origin main -f } - standard: + release: name: Nu needs: prepare + if: needs.prepare.outputs.skip != 'true' strategy: fail-fast: false matrix: @@ -84,24 +127,15 @@ jobs: - armv7-unknown-linux-musleabihf - riscv64gc-unknown-linux-gnu - loongarch64-unknown-linux-gnu - extra: ['bin'] include: - target: aarch64-apple-darwin os: macos-latest - target: x86_64-apple-darwin os: macos-latest - target: x86_64-pc-windows-msvc - extra: 'bin' - os: windows-latest - - target: x86_64-pc-windows-msvc - extra: msi os: windows-latest - target: aarch64-pc-windows-msvc - extra: 'bin' - os: windows-latest - - target: aarch64-pc-windows-msvc - extra: msi - os: windows-latest + os: windows-11-arm - target: x86_64-unknown-linux-gnu os: ubuntu-22.04 - target: x86_64-unknown-linux-musl @@ -120,40 +154,64 @@ jobs: os: ubuntu-22.04 runs-on: ${{matrix.os}} - steps: - uses: actions/checkout@v4 with: ref: main fetch-depth: 0 + - name: Install Wix Toolset 6 for Windows + shell: pwsh + if: ${{ startsWith(matrix.os, 'windows') }} + run: | + dotnet tool install --global wix --version 6.0.0 + dotnet workload install wix + $wixPath = "$env:USERPROFILE\.dotnet\tools" + echo "$wixPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + $env:PATH = "$wixPath;$env:PATH" + wix --version + - name: Update Rust Toolchain Target run: | echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml - name: Setup Rust toolchain and cache - uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 + uses: actions-rust-lang/setup-rust-toolchain@v1 # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` with: rustflags: '' - name: Setup Nushell uses: hustcer/setup-nu@v3 + if: ${{ matrix.os != 'windows-11-arm' }} with: version: 0.103.0 - name: Release Nu Binary id: nu + if: ${{ matrix.os != 'windows-11-arm' }} run: nu .github/workflows/release-pkg.nu env: OS: ${{ matrix.os }} REF: ${{ github.ref }} TARGET: ${{ matrix.target }} - _EXTRA_: ${{ matrix.extra }} + + - name: Build Nu for Windows ARM64 + id: nu0 + shell: pwsh + if: ${{ matrix.os == 'windows-11-arm' }} + run: | + $env:OS = 'windows' + $env:REF = '${{ github.ref }}' + $env:TARGET = '${{ matrix.target }}' + cargo build --release --all --target aarch64-pc-windows-msvc + cp ./target/${{ matrix.target }}/release/nu.exe . + ./nu.exe -c 'version' + ./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu - name: Create an Issue for Release Failure if: ${{ failure() }} - uses: JasonEtco/create-an-issue@v2.9.2 + uses: JasonEtco/create-an-issue@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -161,13 +219,6 @@ jobs: search_existing: open filename: .github/AUTO_ISSUE_TEMPLATE/nightly-build-fail.md - - name: Set Outputs of Short SHA - id: vars - run: | - echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT - sha_short=$(git rev-parse --short HEAD) - echo "sha_short=${sha_short:0:7}" >> $GITHUB_OUTPUT - # REF: https://github.com/marketplace/actions/gh-release # Create a release only in nushell/nightly repo - name: Publish Archive @@ -175,9 +226,39 @@ jobs: if: ${{ startsWith(github.repository, 'nushell/nightly') }} with: prerelease: true - files: ${{ steps.nu.outputs.archive }} - tag_name: nightly-${{ steps.vars.outputs.sha_short }} - name: Nu-nightly-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.sha_short }} + files: | + ${{ steps.nu.outputs.msi }} + ${{ steps.nu0.outputs.msi }} + ${{ steps.nu.outputs.archive }} + ${{ steps.nu0.outputs.archive }} + tag_name: ${{ needs.prepare.outputs.nightly_tag }} + name: ${{ needs.prepare.outputs.build_date }}-${{ needs.prepare.outputs.nightly_tag }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + sha256sum: + needs: [prepare, release] + name: Create Sha256sum + runs-on: ubuntu-latest + if: github.repository == 'nushell/nightly' + steps: + - name: Download Release Archives + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: >- + gh release download ${{ needs.prepare.outputs.nightly_tag }} + --repo ${{ github.repository }} + --pattern '*' + --dir release + - name: Create Checksums + run: cd release && shasum -a 256 * > ../SHA256SUMS + - name: Publish Checksums + uses: softprops/action-gh-release@v2.0.9 + with: + draft: false + prerelease: true + files: SHA256SUMS + tag_name: ${{ needs.prepare.outputs.nightly_tag }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -185,12 +266,9 @@ jobs: name: Cleanup # Should only run in nushell/nightly repo if: github.repository == 'nushell/nightly' + needs: [release, sha256sum] runs-on: ubuntu-latest steps: - # Sleep for 30 minutes, waiting for the release to be published - - name: Waiting for Release - run: sleep 1800 - - uses: actions/checkout@v4 with: ref: main @@ -205,7 +283,7 @@ jobs: shell: nu {0} run: | let KEEP_COUNT = 10 - let deprecated = (http get https://api.github.com/repos/nushell/nightly/releases | sort-by -r created_at | select tag_name id | range $KEEP_COUNT..) + let deprecated = (http get https://api.github.com/repos/nushell/nightly/releases | sort-by -r created_at | select tag_name id | slice $KEEP_COUNT..) for release in $deprecated { print $'Deleting tag ($release.tag_name)' git push origin --delete $release.tag_name diff --git a/.github/workflows/release-pkg.nu b/.github/workflows/release-pkg.nu index de71325d43..ac7bbc0e9b 100755 --- a/.github/workflows/release-pkg.nu +++ b/.github/workflows/release-pkg.nu @@ -175,49 +175,56 @@ if $os in ['macos-latest'] or $USE_UBUNTU { tar -czf $archive $dest print $'archive: ---> ($archive)'; ls $archive # REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ - echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT + echo $"archive=($archive)(char nl)" o>> $env.GITHUB_OUTPUT } else if $os =~ 'windows' { let releaseStem = $'($bin)-($version)-($target)' + let arch = if $nu.os-info.arch =~ 'x86_64' { 'x64' } else { 'arm64' } + fetch-less $arch - print $'(char nl)Download less related stuffs...'; hr-line - # todo: less-v661 is out but is released as a zip file. maybe we should switch to that and extract it? - aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe - # the below was renamed because it was failing to download for darren. it should work but it wasn't - # todo: maybe we should get rid of this aria2c dependency and just use http get? - #aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt - aria2c https://github.com/jftuga/less-Windows/blob/master/LICENSE -o LICENSE-for-less.txt - - # Create Windows msi release package - if (get-env _EXTRA_) == 'msi' { - - let wixRelease = $'($src)/target/wix/($releaseStem).msi' - print $'(char nl)Start creating Windows msi package with the following contents...' - cd $src; hr-line - # Wix need the binaries be stored in target/release/ - cp -r ($'($dist)/*' | into glob) target/release/ - ls target/release/* | print - cargo install cargo-wix --version 0.3.8 - cargo wix --no-build --nocapture --package nu --output $wixRelease + print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls | print + let archive = $'($dist)/($releaseStem).zip' + 7z a $archive ...(glob *) + let pkg = (ls -f $archive | get name) + if not ($pkg | is-empty) { # Workaround for https://github.com/softprops/action-gh-release/issues/280 - let archive = ($wixRelease | str replace --all '\' '/') - print $'archive: ---> ($archive)'; - echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT - - } else { - - print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls | print - let archive = $'($dist)/($releaseStem).zip' - 7z a $archive ...(glob *) - let pkg = (ls -f $archive | get name) - if not ($pkg | is-empty) { - # Workaround for https://github.com/softprops/action-gh-release/issues/280 - let archive = ($pkg | get 0 | str replace --all '\' '/') - print $'archive: ---> ($archive)' - echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT - } + let archive = ($pkg | get 0 | str replace --all '\' '/') + print $'archive: ---> ($archive)' + echo $"archive=($archive)(char nl)" o>> $env.GITHUB_OUTPUT } + + # Create extra Windows msi release package if dotnet and wix are available + let installed = [dotnet wix] | all { (which $in | length) > 0 } + if $installed and (wix --version | split row . | first | into int) >= 6 { + + print $'(char nl)Start creating Windows msi package with the following contents...' + cd $src; cd wix; hr-line; mkdir nu + # Wix need the binaries be stored in nu folder + cp -r ($'($dist)/*' | into glob) nu/ + cp $'($dist)/README.txt' . + ls -f nu/* | print + ./nu/nu.exe -c $'NU_RELEASE_VERSION=($version) dotnet build -c Release -p:Platform=($arch)' + glob **/*.msi | print + # Workaround for https://github.com/softprops/action-gh-release/issues/280 + let wixRelease = (glob **/*.msi | where $it =~ bin | get 0 | str replace --all '\' '/') + let msi = $'($wixRelease | path dirname)/nu-($version)-($target).msi' + mv $wixRelease $msi + print $'MSI archive: ---> ($msi)'; + echo $"msi=($msi)(char nl)" o>> $env.GITHUB_OUTPUT + } +} + +def fetch-less [ + arch: string = 'x64' # The architecture to fetch +] { + let less_zip = $'less-($arch).zip' + print $'Fetching less archive: (ansi g)($less_zip)(ansi reset)' + let url = $'https://github.com/jftuga/less-Windows/releases/download/less-v668/($less_zip)' + http get https://github.com/jftuga/less-Windows/blob/master/LICENSE | save -rf LICENSE-for-less.txt + http get $url | save -rf $less_zip + unzip $less_zip + rm $less_zip lesskey.exe } def 'cargo-build-nu' [] { diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6fcc71eebe..66f8968caa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,24 +35,15 @@ jobs: - armv7-unknown-linux-musleabihf - riscv64gc-unknown-linux-gnu - loongarch64-unknown-linux-gnu - extra: ['bin'] include: - target: aarch64-apple-darwin os: macos-latest - target: x86_64-apple-darwin os: macos-latest - target: x86_64-pc-windows-msvc - extra: 'bin' - os: windows-latest - - target: x86_64-pc-windows-msvc - extra: msi os: windows-latest - target: aarch64-pc-windows-msvc - extra: 'bin' - os: windows-latest - - target: aarch64-pc-windows-msvc - extra: msi - os: windows-latest + os: windows-11-arm - target: x86_64-unknown-linux-gnu os: ubuntu-22.04 - target: x86_64-unknown-linux-musl @@ -75,6 +66,17 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install Wix Toolset 6 for Windows + shell: pwsh + if: ${{ startsWith(matrix.os, 'windows') }} + run: | + dotnet tool install --global wix --version 6.0.0 + dotnet workload install wix + $wixPath = "$env:USERPROFILE\.dotnet\tools" + echo "$wixPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + $env:PATH = "$wixPath;$env:PATH" + wix --version + - name: Update Rust Toolchain Target run: | echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml @@ -88,17 +90,31 @@ jobs: - name: Setup Nushell uses: hustcer/setup-nu@v3 + if: ${{ matrix.os != 'windows-11-arm' }} with: version: 0.103.0 - name: Release Nu Binary id: nu + if: ${{ matrix.os != 'windows-11-arm' }} run: nu .github/workflows/release-pkg.nu env: OS: ${{ matrix.os }} REF: ${{ github.ref }} TARGET: ${{ matrix.target }} - _EXTRA_: ${{ matrix.extra }} + + - name: Build Nu for Windows ARM64 + id: nu0 + shell: pwsh + if: ${{ matrix.os == 'windows-11-arm' }} + run: | + $env:OS = 'windows' + $env:REF = '${{ github.ref }}' + $env:TARGET = '${{ matrix.target }}' + cargo build --release --all --target aarch64-pc-windows-msvc + cp ./target/${{ matrix.target }}/release/nu.exe . + ./nu.exe -c 'version' + ./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu # WARN: Don't upgrade this action due to the release per asset issue. # See: https://github.com/softprops/action-gh-release/issues/445 @@ -107,7 +123,11 @@ jobs: if: ${{ startsWith(github.ref, 'refs/tags/') }} with: draft: true - files: ${{ steps.nu.outputs.archive }} + files: | + ${{ steps.nu.outputs.msi }} + ${{ steps.nu0.outputs.msi }} + ${{ steps.nu.outputs.archive }} + ${{ steps.nu0.outputs.archive }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/build.rs b/scripts/build.rs index e366b4998c..714f931bdd 100644 --- a/scripts/build.rs +++ b/scripts/build.rs @@ -3,7 +3,7 @@ fn main() { let mut res = winresource::WindowsResource::new(); res.set("ProductName", "Nushell"); res.set("FileDescription", "Nushell"); - res.set("LegalCopyright", "Copyright (C) 2022"); + res.set("LegalCopyright", "Copyright (C) 2025"); res.set_icon("assets/nu_logo.ico"); res.compile() .expect("Failed to run the Windows resource compiler (rc.exe)"); diff --git a/wix/License.rtf b/wix/License.rtf index 54e26fad006a332a481e8a3f3ab2a83c18cb3c9f..9e78b15aefd7703836de3cac2c733b4ea7897dd7 100644 GIT binary patch delta 18 ZcmeC+>g1ZRVB$h8M$?UFY*?6hxd1foBNfKg%M0xd?9ji+r`05nqt!vFvP diff --git a/wix/main.wixproj b/wix/main.wixproj new file mode 100644 index 0000000000..ccf018e0bd --- /dev/null +++ b/wix/main.wixproj @@ -0,0 +1,36 @@ + + + Package + nu-$(Platform) + 82D756D2-19FA-4F09-B10F-64942E89F364 + + SourceDir=$(MSBuildProjectDirectory)\nu; + + true + ICE80 + + + + x64 + $(DefineConstants) + + + + arm64 + $(DefineConstants) + + + + + + + + + + + + + + + + diff --git a/wix/main.wxs b/wix/main.wxs index 9b7ea91e3a..46ce3bf929 100644 --- a/wix/main.wxs +++ b/wix/main.wxs @@ -1,468 +1,297 @@ - - + - - - - - - - - + - + + + + + - + + - + - - - - + + - - - - + + + + + - 1. Comment out or remove the `Component` tag along with its contents. - 2. Comment out or remove the `ComponentRef` tag with the "License" Id - attribute value further down in this file. - --> - - - + + + + - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + - - - - + + + + + + + - + + + + - - + + + - + + - - - - - - - "")]]> - - + + + + - - - + + - --> - - + + - + + + + - - + + + + - + + - The banner BMP dimensions are 493 x 58 pixels. - --> - - - - - - - + + + + + + diff --git a/wix/nu.ico b/wix/nu.ico new file mode 100644 index 0000000000000000000000000000000000000000..b74f2a336c7286797fc5b2da0b98d1b2a82a27ad GIT binary patch literal 7488 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

+j&@!zrF?CY=Atyg}DKaY9M6__XDf)bocV zcPZ!s>!fMo$RYX#15X%clF${n*^pBR1msV1x#1sP>a)Ey%YinOX{#D9(m=v)Ggy-` z$!vSDAtgY(a=Oluz(|qD5q99)&cq2*XxpN8rm7VFA5@a{FN4SrRyq;Ie zIA=c?>g4&a{O2TkFKp*zA=>k;y=HK8pe6*KfzAoYT?8VOz=k(ESr+aNmRs0T@d{1b zAQC{psF-9gl~nTBN#VelwI|_YR_8-cfFvr@$^FVf8-rZf6*rrqn!##HB4g_u3@Uy* zB;mxtu8WK-2%Sd*ml_ZP99T8y*V+|46(c)1wq($Zw=r4Dwu^Ocx%q-934P;x{vW=fGyD8Ad9vA4bOY+!c^@fS0>$PK zdX=OShF|6C2*b4MFNLb?Dh8x%N2siBpaY@)O+H6?3O;(C1sm!}N&p+c3)_~;=WI~{ z$E9^@D9=q|7JG#rw=|eS4cCP{jYiD>umPUVM2xjizgG`5Pj5jJsO$g1PJN+qMr}1} z<_~U=_LMzloIwjY@Zdh}KXLoyKpRjvf_`1gnjN|soyN`6Xr}{_w;x-dZ*0pU3s!Xx zDR94AeEkfVV^LZ-l8Q(5}>fDrZ;2We>n;moh+oo4S zQ-{6p)O7yktIDRz?L&w)U;TeJ>;G6nukw*dOqQ9KS&jSlDF+IW2P=OlmofhDe*n6_ B*OUMN literal 0 HcmV?d00001