Add support for 64bit updaters on windows

This commit is contained in:
Caelan Sayler
2025-07-20 18:58:32 +01:00
parent 65df343dea
commit eaad849f2c
7 changed files with 262 additions and 65 deletions

View File

@@ -3,38 +3,13 @@ on:
workflow_call:
jobs:
windows-bins:
runs-on: windows-latest
env:
RUSTFLAGS: -C target-feature=+crt-static
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Job Environment
uses: ./.github/actions/job-setup
with:
rust-cache: true
- name: Install Dependencies
run: |
rustup component add rust-src --toolchain nightly-x86_64-pc-windows-msvc
- name: Build Rust Binaries
run: cargo +nightly build --target i686-win7-windows-msvc --features windows --release -Z build-std="core,alloc,std,panic_abort" -p velopack_bins
- name: Upload Rust Build Artifacts
uses: actions/upload-artifact@v4
with:
name: rust-windows-bins
path: |
target\i686-win7-windows-msvc\release\*.exe
target\i686-win7-windows-msvc\release\*.pdb
retention-days: 1
windows:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
target: [x86, x64, arm64]
type: [bins, libs]
include:
- target: x86
rust_flags: --release -Z build-std="core,alloc,std,panic_abort"
@@ -59,21 +34,28 @@ jobs:
run: |
rustup component add rust-src --toolchain nightly-x86_64-pc-windows-msvc
rustup target add aarch64-pc-windows-msvc --toolchain nightly-x86_64-pc-windows-msvc
- name: Build Rust (${{ matrix.target }})
mkdir ./artifacts
- name: Build Rust Binaries (${{ matrix.target }})
if: ${{ matrix.type == 'bins' }}
run: |
cargo +nightly build --target ${{ matrix.rust_target }} --features windows ${{ matrix.rust_flags }} -p velopack_bins
copy target\${{ matrix.rust_target }}\release\update.exe artifacts\UpdateWin_${{ matrix.target }}.exe
copy target\${{ matrix.rust_target }}\release\setup.exe artifacts\SetupWin_${{ matrix.target }}.exe
copy target\${{ matrix.rust_target }}\release\stub.exe artifacts\StubWin_${{ matrix.target }}.exe
- name: Build Rust Libraries (${{ matrix.target }})
if: ${{ matrix.type == 'libs' }}
run: |
cargo +nightly build --target ${{ matrix.rust_target }} ${{ matrix.rust_flags }} -p velopack_nodeffi -p velopack_libc
move target\${{ matrix.rust_target }}\release\velopack_nodeffi.dll target\velopack_nodeffi_win_${{ matrix.target }}_msvc.node
move target\${{ matrix.rust_target }}\release\velopack_libc.dll target\velopack_libc_win_${{ matrix.target }}_msvc.dll
move target\${{ matrix.rust_target }}\release\velopack_libc.dll.lib target\velopack_libc_win_${{ matrix.target }}_msvc.dll.lib
move target\${{ matrix.rust_target }}\release\velopack_libc.lib target\velopack_libc_win_${{ matrix.target }}_msvc.lib
copy target\${{ matrix.rust_target }}\release\velopack_nodeffi.dll artifacts\velopack_nodeffi_win_${{ matrix.target }}_msvc.node
copy target\${{ matrix.rust_target }}\release\velopack_libc.dll artifacts\velopack_libc_win_${{ matrix.target }}_msvc.dll
copy target\${{ matrix.rust_target }}\release\velopack_libc.dll.lib artifacts\velopack_libc_win_${{ matrix.target }}_msvc.dll.lib
copy target\${{ matrix.rust_target }}\release\velopack_libc.lib artifacts\velopack_libc_win_${{ matrix.target }}_msvc.lib
- name: Upload Rust Build Artifacts
uses: actions/upload-artifact@v4
with:
name: rust-windows-libs-${{ matrix.target }}
name: rust-windows-${{ matrix.type }}-${{ matrix.target }}
path: |
target\*.node
target\*.dll
target\*.lib
artifacts\*
retention-days: 1
- name: Cancel workflow if failed
uses: andymckay/cancel-action@0.5
@@ -113,9 +95,9 @@ jobs:
path: |
artifacts/UpdateNix_${{ matrix.target }}
retention-days: 1
# - name: Cancel workflow if failed
# uses: andymckay/cancel-action@0.5
# if: ${{ failure() }}
- name: Cancel workflow if failed
uses: andymckay/cancel-action@0.5
if: ${{ failure() }}
linux:
strategy:
@@ -157,9 +139,9 @@ jobs:
artifacts/*.node
artifacts/*.a
retention-days: 1
# - name: Cancel workflow if failed
# uses: andymckay/cancel-action@0.5
# if: ${{ failure() }}
- name: Cancel workflow if failed
uses: andymckay/cancel-action@0.5
if: ${{ failure() }}
macos:
runs-on: macos-latest
@@ -174,6 +156,7 @@ jobs:
- name: Install Dependencies
run: |
rustup target add x86_64-apple-darwin
mkdir ./artifacts
- name: Build Rust (x64)
run: |
cargo build --release --target x86_64-apple-darwin
@@ -184,23 +167,20 @@ jobs:
otool -L ./target/aarch64-apple-darwin/release/update
- name: Create Universal Binary
run: |
lipo -create -output ./target/UpdateMac ./target/x86_64-apple-darwin/release/update ./target/aarch64-apple-darwin/release/update
file ./target/UpdateMac
lipo -create -output ./target/velopack_nodeffi_osx.node ./target/x86_64-apple-darwin/release/libvelopack_nodeffi.dylib ./target/aarch64-apple-darwin/release/libvelopack_nodeffi.dylib
file ./target/velopack_nodeffi_osx.node
lipo -create -output ./target/velopack_libc_osx.dylib ./target/x86_64-apple-darwin/release/libvelopack_libc.dylib ./target/aarch64-apple-darwin/release/libvelopack_libc.dylib
file ./target/velopack_libc_osx.dylib
cp ./target/x86_64-apple-darwin/release/libvelopack_libc.a ./target/velopack_libc_osx_x64_gnu.a
cp ./target/aarch64-apple-darwin/release/libvelopack_libc.a ./target/velopack_libc_osx_arm64_gnu.a
lipo -create -output ./artifacts/UpdateMac ./target/x86_64-apple-darwin/release/update ./target/aarch64-apple-darwin/release/update
file ./artifacts/UpdateMac
lipo -create -output ./artifacts/velopack_nodeffi_osx.node ./target/x86_64-apple-darwin/release/libvelopack_nodeffi.dylib ./target/aarch64-apple-darwin/release/libvelopack_nodeffi.dylib
file ./artifacts/velopack_nodeffi_osx.node
lipo -create -output ./artifacts/velopack_libc_osx.dylib ./target/x86_64-apple-darwin/release/libvelopack_libc.dylib ./target/aarch64-apple-darwin/release/libvelopack_libc.dylib
file ./artifacts/velopack_libc_osx.dylib
cp ./target/x86_64-apple-darwin/release/libvelopack_libc.a ./artifacts/velopack_libc_osx_x64_gnu.a
cp ./target/aarch64-apple-darwin/release/libvelopack_libc.a ./artifacts/velopack_libc_osx_arm64_gnu.a
- name: Upload Rust Build Artifacts
uses: actions/upload-artifact@v4
with:
name: rust-macos
path: |
target/UpdateMac
target/*.dylib
target/*.node
target/*.a
artifacts/*
retention-days: 1
- name: Cancel workflow if failed
uses: andymckay/cancel-action@0.5

View File

@@ -116,7 +116,7 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_wait_seconds: 900
artifacts: rust-macos,rust-windows-bins,rust-ubuntu-bins-x64,rust-ubuntu-bins-arm64
artifacts: rust-macos,rust-windows-bins-x86,rust-windows-bins-x64,rust-windows-bins-arm64,rust-ubuntu-bins-x64,rust-ubuntu-bins-arm64
verbose: true
- name: Download Rust Artifacts
uses: actions/download-artifact@v4

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
.claude/
target/
_docyml/

123
CLAUDE.md Normal file
View File

@@ -0,0 +1,123 @@
# Claude Development Notes for Velopack
This file contains important technical information about the Velopack codebase that may be useful for future development tasks.
## What is Velopack
Velopack is an application deployment and auto-update framework. It creates installer packages and handles automatic updates for desktop applications across Windows, macOS, and Linux.
## The "Pack" Process
The pack process creates releases and updates for applications:
1. **Input**: Application files and metadata (version, architecture, etc.)
2. **Output**:
- `.nupkg` file: Contains the application update package
- `Setup.exe` (Windows): Installer that contains embedded binaries and the update package
- `releases.json`: Metadata about available releases
## Update Package Structure (.nupkg)
Update packages are ZIP files with a specific structure:
```
MyApp-1.0.0-full.nupkg
├── MyApp.nuspec # Package metadata (version, architecture, etc.)
├── [Content_Types].xml # ZIP metadata
├── _rels/ # ZIP metadata
└── lib/
└── app/
├── Squirrel.exe # Update binary (handles app updates)
├── myapp.exe # Main application executable
├── myapp_ExecutionStub.exe # Stub executable
├── sq.version # Version information
└── [other app files]
```
### Key Discoveries
- **Squirrel.exe IS the Update.exe binary**: The update binary is packaged as `Squirrel.exe` in `lib/app/`, not as a separate vendor file
- **Architecture-specific updates**: Each package contains the appropriate architecture version of Squirrel.exe for the target platform
- **Setup.exe vs .nupkg**: The installer (Setup.exe) contains embedded binaries, while the update package (.nupkg) contains the application files and update binary
## Binary Locations and Architecture
### Platform Binary Naming
- **Windows**: `UpdateWin_x86.exe`, `UpdateWin_x64.exe`, `UpdateWin_arm64.exe`
- **Linux**: `UpdateNix_x64`, `UpdateNix_arm64`
- **macOS**: `UpdateMac` (universal binary)
### Build Locations
- **Debug builds**: `target/debug/` with generic names (`update.exe`, `setup.exe`, `stub.exe`)
- **Release builds**: `target/release/` with architecture-specific names
### Debug vs Release Mode
- **HelperFile.cs** contains `#if DEBUG` conditionals to handle different binary naming between debug and release modes
- Debug mode uses generic binary names for local development
- Release mode uses architecture-specific names for production builds
## Testing Package Contents
To verify package contents in tests:
1. Extract the `.nupkg` file (it's a ZIP archive)
2. Check `lib/app/Squirrel.exe` for the update binary
3. Use AsmResolver (`PEFile.FromFile()` and `peFile.FileHeader.Machine`) to verify binary architecture
4. Parse the `.nuspec` file for metadata verification
## Build Instructions
### Rust Binaries
Build the core Rust binaries (update.exe, setup.exe, stub.exe):
```bash
# Build for Windows (debug)
cargo build --features windows
# Build for Windows (release)
cargo build --features windows --release
# Build for specific Windows architecture (release)
cargo build --features windows --release --target x86_64-pc-windows-msvc
cargo build --features windows --release --target i686-pc-windows-msvc
cargo build --features windows --release --target aarch64-pc-windows-msvc
```
### .NET Build
Build the packaging and tooling:
```bash
# Build in debug mode
dotnet build
# Build in release mode
dotnet build -c Release
# Run specific test with detailed output
dotnet test --filter "TestName" --logger "console;verbosity=normal" path/to/test.csproj
```
---
## Instructions for Future Development
**⚠️ Important for AI Agents**: When working on this codebase, please update this CLAUDE.md file as you discover new generic technical knowledge that would benefit future development tasks.
### What to Add:
-**General architecture and design patterns**
-**Build processes and commands**
-**File structures and important discoveries**
-**Testing approaches and utilities**
-**Cross-platform differences**
### What NOT to Add:
-**Task-specific implementation details**
-**Temporary status updates**
-**Personal development notes**
### How to Update:
1. Add new sections or expand existing ones with discovered knowledge
2. Keep information generic and applicable to various future tasks
3. Focus on technical insights that aren't immediately obvious from the code
4. Use clear examples and structure for easy reference
This file should serve as a growing knowledge base for understanding Velopack's architecture and development processes.

View File

@@ -38,9 +38,11 @@
<None Include="$(MSBuildThisFileDirectory)..\artwork\DefaultApp.png" Pack="true" PackagePath="vendor" />
</ItemGroup>
<ItemGroup Condition=" '$(PackRustAssets)' == 'true' ">
<None Include="$(MSBuildThisFileDirectory)..\target\release\update.exe" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\setup.exe" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\stub.exe" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\UpdateWin_x86.exe" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\UpdateWin_x64.exe" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\UpdateWin_arm64.exe" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\SetupWin_x86.exe" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\StubWin_x86.exe" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\UpdateMac" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\UpdateNix_x64" Pack="true" PackagePath="vendor" />
<None Include="$(MSBuildThisFileDirectory)..\target\release\UpdateNix_arm64" Pack="true" PackagePath="vendor" />

View File

@@ -8,15 +8,29 @@ public static class HelperFile
{
private static string GetUpdateExeName(RID target, ILogger log)
{
#if DEBUG
switch (target.BaseRID) {
case RuntimeOs.Windows:
return FindHelperFile("update.exe");
#if DEBUG
case RuntimeOs.Linux:
return FindHelperFile("update");
case RuntimeOs.OSX:
return FindHelperFile("update");
}
#else
switch (target.BaseRID) {
case RuntimeOs.Windows:
if (!target.HasArchitecture) {
log.LogWarning("No architecture specified with --runtime, defaulting to x86. If this was not intended please specify via the --runtime parameter");
return FindHelperFile("UpdateWin_x86.exe");
}
return target.Architecture switch {
RuntimeCpu.arm64 => FindHelperFile("UpdateWin_arm64.exe"),
RuntimeCpu.x64 => FindHelperFile("UpdateWin_x64.exe"),
RuntimeCpu.x86 => FindHelperFile("UpdateWin_x86.exe"),
_ => throw new PlatformNotSupportedException($"Update binary is not available for this platform ({target}).")
};
case RuntimeOs.Linux:
if (!target.HasArchitecture) {
log.LogWarning("No architecture specified with --runtime, defaulting to x64. If this was not intended please specify via the --runtime parameter");
@@ -30,8 +44,8 @@ public static class HelperFile
};
case RuntimeOs.OSX:
return FindHelperFile("UpdateMac");
#endif
}
#endif
throw new PlatformNotSupportedException($"Update binary is not available for this platform ({target}).");
}
@@ -63,9 +77,19 @@ public static class HelperFile
public static string AppImageRuntimeX86 => FindHelperFile("appimagekit-runtime-i686");
public static string SetupPath => FindHelperFile("setup.exe");
public static string SetupPath =>
#if DEBUG
FindHelperFile("setup.exe");
#else
FindHelperFile("SetupWin_x86.exe");
#endif
public static string StubExecutablePath => FindHelperFile("stub.exe");
public static string StubExecutablePath =>
#if DEBUG
FindHelperFile("stub.exe");
#else
FindHelperFile("StubWin_x86.exe");
#endif
[SupportedOSPlatform("windows")]
public static string WixTemplatePath => FindHelperFile("wix\\template.wxs");

View File

@@ -2,6 +2,9 @@
using System.Globalization;
using System.Runtime.Versioning;
using System.Xml.Linq;
using AsmResolver.PE;
using AsmResolver.PE.File;
using AsmResolver.PE.File.Headers;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Win32;
using NuGet.Packaging;
@@ -870,7 +873,7 @@ public class WindowsPackTests
}
private static void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger,
bool addNewFile = false, string assemblyNameOverride = null)
bool addNewFile = false, string assemblyNameOverride = null, string targetRuntime = "win-x64")
{
var projDir = PathHelper.GetTestRootPath("TestApp");
var testStringFile = Path.Combine(projDir, "Const.cs");
@@ -879,7 +882,7 @@ public class WindowsPackTests
try {
File.WriteAllText(testStringFile, $"class Const {{ public const string TEST_STRING = \"{testString}\"; }}");
var args = new List<string> {
"publish", "--no-self-contained", "-c", "Release", "-r", "win-x64", "-o", "publish", "--tl:off"
"publish", "--no-self-contained", "-c", "Release", "-r", targetRuntime, "-o", "publish", "--tl:off"
};
if (assemblyNameOverride != null) {
@@ -925,7 +928,7 @@ public class WindowsPackTests
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
PackVersion = version,
TargetRuntime = RID.Parse("win-x64"),
TargetRuntime = RID.Parse(targetRuntime),
PackDirectory = Path.Combine(projDir, "publish"),
};
@@ -935,4 +938,69 @@ public class WindowsPackTests
File.WriteAllText(testStringFile, oldText);
}
}
[SkippableTheory]
[InlineData("x86", MachineType.I386)]
[InlineData("x64", MachineType.Amd64)]
[InlineData("arm64", MachineType.Arm64)]
public void PackIncludesCorrectArchitectureUpdateBinary(string architecture, MachineType expectedMachineType)
{
Skip.IfNot(VelopackRuntimeInfo.IsWindows);
using var logger = _output.BuildLoggerFor<WindowsPackTests>();
using var _1 = TempUtil.GetTempDirectory(out var tmpOutput);
using var _2 = TempUtil.GetTempDirectory(out var tmpReleaseDir);
var unzipDir = @"C:\Source\velopack\test_inspect_" + architecture;
var exe = "testapp.exe";
var id = "Test.Squirrel-App";
var version = "1.0.0";
PathHelper.CopyRustAssetTo(exe, tmpOutput);
var options = new WindowsPackOptions {
EntryExecutableName = exe,
ReleaseDir = new DirectoryInfo(tmpReleaseDir),
PackId = id,
PackVersion = version,
TargetRuntime = RID.Parse($"win-{architecture}"),
PackDirectory = tmpOutput,
};
var runner = GetPackRunner(logger);
runner.Run(options).GetAwaiterResult();
var nupkgPath = Path.Combine(tmpReleaseDir, $"{id}-{version}-full.nupkg");
Assert.True(File.Exists(nupkgPath));
// Create extraction directory
if (Directory.Exists(unzipDir))
Directory.Delete(unzipDir, true);
Directory.CreateDirectory(unzipDir);
EasyZip.ExtractZipToDirectory(logger.ToVelopackLogger(), nupkgPath, unzipDir);
_output.WriteLine($"Package extracted to: {unzipDir}");
_output.WriteLine("Contents:");
foreach (var file in Directory.GetFiles(unzipDir, "*", SearchOption.AllDirectories))
{
_output.WriteLine($" {file.Replace(unzipDir, "")}");
}
// Check if Squirrel.exe is the Update.exe binary
var squirrelExePath = Path.Combine(unzipDir, "lib", "app", "Squirrel.exe");
Assert.True(File.Exists(squirrelExePath), "Expected Squirrel.exe to be present in the package");
// Use AsmResolver to verify the binary architecture
var peFile = PEFile.FromFile(squirrelExePath);
var actualMachineType = peFile.FileHeader.Machine;
_output.WriteLine($"Squirrel.exe architecture: {actualMachineType}, expected: {expectedMachineType}");
Assert.True(expectedMachineType == actualMachineType,
$"Squirrel.exe should be {expectedMachineType} architecture but was {actualMachineType}");
_output.WriteLine($"Successfully verified {architecture} architecture in package");
}
}