From a8e40defb85d3ae63ad493b2a6eb8739e47f6d64 Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Fri, 14 Jun 2024 22:36:16 +0100 Subject: [PATCH] Add Linux Arm64 support --- .editorconfig | 14 +++++ .github/workflows/build.yml | 39 ++++++++----- src/Directory.Build.targets | 3 +- src/Rust/Cross.toml | 5 ++ src/Rust/build.rs | 28 ++++++++-- src/Rust/src/shared/runtime_arch.rs | 4 +- .../Commands/LinuxPackCommandRunner.cs | 56 ++++++++++--------- .../Commands/OsxPackCommandRunner.cs | 2 +- .../Commands/WindowsPackCommandRunner.cs | 2 +- src/Velopack.Packaging/HelperFile.cs | 26 ++++++--- src/Velopack.Packaging/PackageBuilder.cs | 1 - 11 files changed, 119 insertions(+), 61 deletions(-) create mode 100644 src/Rust/Cross.toml diff --git a/.editorconfig b/.editorconfig index e7128c76..9cac4b23 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,7 @@ indent_style = space # ReSharper properties resharper_csharp_wrap_after_invocation_lpar = true resharper_csharp_wrap_arguments_style = chop_if_long +resharper_indent_raw_literal_string = indent # MSBuild files [*.{csproj,targets,props}] @@ -138,6 +139,19 @@ dotnet_diagnostic.il3000.severity = error dotnet_diagnostic.il3001.severity = error dotnet_diagnostic.il3002.severity = error dotnet_diagnostic.il3003.severity = error + +# Resharper +resharper_place_accessorholder_attribute_on_same_line = false +resharper_place_accessor_attribute_on_same_line = false +resharper_place_event_attribute_on_same_line = false +resharper_place_field_attribute_on_same_line = false +resharper_place_method_attribute_on_same_line = false +resharper_place_property_attribute_on_same_line = false +resharper_place_record_field_attribute_on_same_line = false +resharper_place_type_attribute_on_same_line = false +csharp_max_attribute_length_for_same_line = 0 + + ############################### # VB Coding Conventions # ############################### diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7a92d42..966c9f32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,16 +17,15 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: dotnet/nbgv@master + with: + setAllVars: true - uses: Swatinem/rust-cache@v2 with: key: "rust-test-${{ matrix.os }}" workspaces: "src/Rust -> target" - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - - name: Install NBGV - run: dotnet tool install -g nbgv - if: startsWith(matrix.os, 'macos-') - continue-on-error: true - name: Test Rust working-directory: src/Rust run: cargo llvm-cov ${{ matrix.rust_flags }} --cobertura --output-path ../../test/coverage.rust.${{ matrix.os }}.xml @@ -42,6 +41,9 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: dotnet/nbgv@master + with: + setAllVars: true - uses: Swatinem/rust-cache@v2 with: key: "rust-build-windows" @@ -70,20 +72,33 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: dotnet/nbgv@master + id: nbgv + with: + setAllVars: true - uses: Swatinem/rust-cache@v2 with: key: "rust-build-linux" workspaces: "src/Rust -> target" - - name: Build Rust + - name: Build Rust (x64) working-directory: src/Rust run: | cargo build --release --target x86_64-unknown-linux-gnu - cp ./target/x86_64-unknown-linux-gnu/release/update ./target/release/UpdateNix + cp ./target/x86_64-unknown-linux-gnu/release/update ./target/release/UpdateNix_x64 + - name: Build Rust (arm64) + working-directory: src/Rust + env: + CROSS_NuGetPackageVersion: ${{ steps.nbgv.outputs.NuGetPackageVersion }} + run: | + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall cross --no-confirm + cross build --release --target aarch64-unknown-linux-gnu + cp ./target/aarch64-unknown-linux-gnu/release/update ./target/release/UpdateNix_arm64 - name: Upload Rust Build Artifacts uses: actions/upload-artifact@v4 with: name: rust-ubuntu-latest - path: src/Rust/target/release/UpdateNix + path: src/Rust/target/release/UpdateNix* - name: Cancel workflow if failed uses: andymckay/cancel-action@0.4 if: ${{ failure() }} @@ -97,13 +112,13 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: dotnet/nbgv@master + with: + setAllVars: true - uses: Swatinem/rust-cache@v2 with: key: "rust-build-${{ matrix.os }}" workspaces: "src/Rust -> target" - - name: Install NBGV - run: dotnet tool install -g nbgv - continue-on-error: true - name: Build Rust working-directory: src/Rust run: | @@ -170,10 +185,6 @@ jobs: key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} restore-keys: | ${{ runner.os }}-nuget- - - name: Install NBGV - run: dotnet tool install -g nbgv - if: ${{ matrix.os == 'macos-latest' }} - continue-on-error: true - name: Install FUSE run: | sudo add-apt-repository universe diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 4e85e5bd..954b8a39 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -43,7 +43,8 @@ - + + diff --git a/src/Rust/Cross.toml b/src/Rust/Cross.toml new file mode 100644 index 00000000..0c1033a0 --- /dev/null +++ b/src/Rust/Cross.toml @@ -0,0 +1,5 @@ +[target.aarch64-unknown-linux-gnu] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install -y libssl-dev:$CROSS_DEB_ARCH", +] diff --git a/src/Rust/build.rs b/src/Rust/build.rs index dd1c538a..4495bf03 100644 --- a/src/Rust/build.rs +++ b/src/Rust/build.rs @@ -1,6 +1,7 @@ #![allow(unused_variables)] + use semver; -use std::process::Command; +use std::env; #[cfg(target_os = "windows")] extern crate winres; @@ -12,9 +13,7 @@ fn main() { #[cfg(target_os = "windows")] delay_load(); - let ver_output = Command::new("nbgv").args(&["get-version", "-v", "NuGetPackageVersion"]).output().expect("Failed to execute nbgv get-version"); - let version = String::from_utf8(ver_output.stdout).expect("Unable to convert ngbv output to string"); - let version = version.trim(); + let version = get_package_version(); let ver = semver::Version::parse(&version).expect("Unable to parse ngbv output as semver version"); let ver: u64 = ver.major << 48 | ver.minor << 32 | ver.patch << 16; let desc = format!("Velopack {}", version); @@ -28,13 +27,32 @@ fn main() { .set_version_info(winres::VersionInfo::FILEVERSION, ver) .set("CompanyName", "Velopack") .set("ProductName", "Velopack") - .set("ProductVersion", version) + .set("ProductVersion", &version) .set("FileDescription", &desc) .set("LegalCopyright", "Caelan Sayler (c) 2023, Velopack Ltd. (c) 2024") .compile() .unwrap(); } +fn get_package_version() -> String { + if let Ok(version) = env::var("NuGetPackageVersion") { + // NuGetPackageVersion is set, return it trimmed + return version.trim().to_string(); + } else if let Ok(version) = env::var("NBGV_NuGetPackageVersion") { + // NBGV_NuGetPackageVersion is set, return it trimmed + return version.trim().to_string(); + } else if let Ok(version) = env::var("CROSS_NuGetPackageVersion") { + // NBGV_NuGetPackageVersion is set, return it trimmed + return version.trim().to_string(); + } else if env::var("CI").is_ok() { + // CI is set, NuGetPackageVersion should be set always in CI + panic!("Error: NuGetPackageVersion must be set in CI"); + } else { + // CI is not set, return "v0.0.0-local" + return "0.0.0-local".to_string(); + } +} + #[cfg(target_os = "windows")] fn delay_load() { delay_load_exe("update"); diff --git a/src/Rust/src/shared/runtime_arch.rs b/src/Rust/src/shared/runtime_arch.rs index 03a67a33..3018b574 100644 --- a/src/Rust/src/shared/runtime_arch.rs +++ b/src/Rust/src/shared/runtime_arch.rs @@ -1,5 +1,3 @@ -use anyhow::Result; - #[derive(PartialEq, Debug, Clone, strum::IntoStaticStr)] pub enum RuntimeArch { X86, @@ -86,7 +84,7 @@ type IsWow64Process2Fn = unsafe extern "system" fn( ) -> windows::Win32::Foundation::BOOL; #[cfg(target_os = "windows")] -unsafe fn is_wow64_process2(handle: windows::Win32::Foundation::HANDLE) -> Result { +unsafe fn is_wow64_process2(handle: windows::Win32::Foundation::HANDLE) -> anyhow::Result { use windows::Win32::Foundation::TRUE; use windows::Win32::System::SystemInformation::IMAGE_FILE_MACHINE; diff --git a/src/Velopack.Packaging.Unix/Commands/LinuxPackCommandRunner.cs b/src/Velopack.Packaging.Unix/Commands/LinuxPackCommandRunner.cs index f8ab224c..7e04db42 100644 --- a/src/Velopack.Packaging.Unix/Commands/LinuxPackCommandRunner.cs +++ b/src/Velopack.Packaging.Unix/Commands/LinuxPackCommandRunner.cs @@ -35,18 +35,18 @@ public class LinuxPackCommandRunner : PackageBuilder : Options.Categories.TrimEnd(';'); File.WriteAllText(appRunPath, $$""" -#!/bin/sh -if [ ! -z "$APPIMAGE" ] && [ ! -z "$APPDIR" ]; then - MD5=$(echo -n "file://$APPIMAGE" | md5sum | cut -d' ' -f1) - cp "$APPDIR/{{iconFilename}}" "$HOME/.cache/thumbnails/normal/$MD5.png" >/dev/null 2>&1 - cp "$APPDIR/{{iconFilename}}" "$HOME/.cache/thumbnails/large/$MD5.png" >/dev/null 2>&1 - xdg-icon-resource forceupdate >/dev/null 2>&1 -fi -HERE="$(dirname "$(readlink -f "${0}")")" -export PATH="${HERE}"/usr/bin/:"${PATH}" -EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1 | sed 's/\\s/ /g') -exec "${EXEC}" "$@" -""".Replace("\r", "")); + #!/bin/sh + if [ ! -z "$APPIMAGE" ] && [ ! -z "$APPDIR" ]; then + MD5=$(echo -n "file://$APPIMAGE" | md5sum | cut -d' ' -f1) + cp "$APPDIR/{{iconFilename}}" "$HOME/.cache/thumbnails/normal/$MD5.png" >/dev/null 2>&1 + cp "$APPDIR/{{iconFilename}}" "$HOME/.cache/thumbnails/large/$MD5.png" >/dev/null 2>&1 + xdg-icon-resource forceupdate >/dev/null 2>&1 + fi + HERE="$(dirname "$(readlink -f "${0}")")" + export PATH="${HERE}"/usr/bin/:"${PATH}" + EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1 | sed 's/\\s/ /g') + exec "${EXEC}" "$@" + """.Replace("\r", "")); Chmod.ChmodFileAsExecutable(appRunPath); var mainExeName = Options.EntryExecutableName ?? Options.PackId; @@ -59,23 +59,28 @@ exec "${EXEC}" "$@" mainExeName = mainExeName.Replace(" ", "\\s"); File.WriteAllText(Path.Combine(dir.FullName, Options.PackId + ".desktop"), $""" -[Desktop Entry] -Type=Application -Name={Options.PackTitle ?? Options.PackId} -Comment={Options.PackTitle ?? Options.PackId} {Options.PackVersion} -Icon={Options.PackId} -Exec={mainExeName} -StartupWMClass={Options.PackId} -Categories={categories}; -""".Replace("\r", "")); + [Desktop Entry] + Type=Application + Name={Options.PackTitle ?? Options.PackId} + Comment={Options.PackTitle ?? Options.PackId} {Options.PackVersion} + Icon={Options.PackId} + Exec={mainExeName} + StartupWMClass={Options.PackId} + Categories={categories}; + """.Replace("\r", "")); // copy existing app files CopyFiles(new DirectoryInfo(packDir), bin, progress, true); } + Options.TargetRuntime.Architecture = Options.TargetRuntime.HasArchitecture + ? Options.TargetRuntime.Architecture + : GetMachineForBinary(MainExePath); + + // velopack required files File.WriteAllText(Path.Combine(bin.FullName, "sq.version"), GenerateNuspecContent()); - File.Copy(HelperFile.GetUpdatePath(RuntimeOs.Linux), Path.Combine(bin.FullName, "UpdateNix"), true); + File.Copy(HelperFile.GetUpdatePath(Options.TargetRuntime, Log), Path.Combine(bin.FullName, "UpdateNix"), true); progress(100); return Task.FromResult(dir.FullName); } @@ -91,10 +96,7 @@ Categories={categories}; protected override Task CreatePortablePackage(Action progress, string packDir, string outputPath) { progress(-1); - var machine = Options.TargetRuntime.HasArchitecture - ? Options.TargetRuntime.Architecture - : GetMachineForBinary(MainExePath); - AppImageTool.CreateLinuxAppImage(packDir, outputPath, machine, Log); + AppImageTool.CreateLinuxAppImage(packDir, outputPath, Options.TargetRuntime.Architecture, Log); PortablePackagePath = outputPath; progress(100); return Task.CompletedTask; @@ -126,4 +128,4 @@ Categories={categories}; progress(-1); // there is only one "file", so progress will not work return base.CreateDeltaPackage(progress, releasePkg, prevReleasePkg, outputPkg, mode); } -} +} \ No newline at end of file diff --git a/src/Velopack.Packaging.Unix/Commands/OsxPackCommandRunner.cs b/src/Velopack.Packaging.Unix/Commands/OsxPackCommandRunner.cs index aee11b5e..2254d681 100644 --- a/src/Velopack.Packaging.Unix/Commands/OsxPackCommandRunner.cs +++ b/src/Velopack.Packaging.Unix/Commands/OsxPackCommandRunner.cs @@ -32,7 +32,7 @@ public class OsxPackCommandRunner : PackageBuilder var structure = new OsxStructureBuilder(dir.FullName); var macosdir = structure.MacosDirectory; File.WriteAllText(Path.Combine(macosdir, "sq.version"), GenerateNuspecContent()); - File.Copy(HelperFile.GetUpdatePath(RuntimeOs.OSX), Path.Combine(macosdir, "UpdateMac"), true); + File.Copy(HelperFile.GetUpdatePath(Options.TargetRuntime, Log), Path.Combine(macosdir, "UpdateMac"), true); foreach (var f in Directory.GetFiles(macosdir, "*", SearchOption.AllDirectories)) { if (BinDetect.IsMachOImage(f)) { diff --git a/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs b/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs index 0db992c2..3cdafa59 100644 --- a/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs +++ b/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs @@ -46,7 +46,7 @@ public class WindowsPackCommandRunner : PackageBuilder packDir = dir.FullName; var updatePath = Path.Combine(TempDir.FullName, "Update.exe"); - File.Copy(HelperFile.GetUpdatePath(RuntimeOs.Windows), updatePath, true); + File.Copy(HelperFile.GetUpdatePath(Options.TargetRuntime, Log), updatePath, true); // check for and delete clickonce manifest var clickonceManifests = Directory.EnumerateFiles(packDir, "*.application") diff --git a/src/Velopack.Packaging/HelperFile.cs b/src/Velopack.Packaging/HelperFile.cs index 8a4b48f4..013a6df2 100644 --- a/src/Velopack.Packaging/HelperFile.cs +++ b/src/Velopack.Packaging/HelperFile.cs @@ -1,12 +1,13 @@ using System.Runtime.Versioning; +using Microsoft.Extensions.Logging; namespace Velopack.Packaging; public static class HelperFile { - public static string GetUpdateExeName(RuntimeOs os) + private static string GetUpdateExeName(RID target, ILogger log) { - switch (os) { + switch (target.BaseRID) { case RuntimeOs.Windows: return FindHelperFile("update.exe"); #if DEBUG @@ -16,16 +17,25 @@ public static class HelperFile return FindHelperFile("update"); #else case RuntimeOs.Linux: - return FindHelperFile("UpdateNix"); + if (!target.HasArchitecture) { + log.Warn("No architecture specified with --runtime, defaulting to x64. If this was not intended please specify via the --runtime parameter"); + return FindHelperFile("UpdateNix_x64"); + } + + return target.Architecture switch { + RuntimeCpu.arm64 => FindHelperFile("UpdateNix_arm64"), + RuntimeCpu.x64 => FindHelperFile("UpdateNix_x64"), + _ => throw new PlatformNotSupportedException($"Update binary is not available for this platform ({target}).") + }; case RuntimeOs.OSX: return FindHelperFile("UpdateMac"); #endif - default: - throw new PlatformNotSupportedException("Update binary is not available for this platform."); } + + throw new PlatformNotSupportedException($"Update binary is not available for this platform ({target})."); } - public static string GetUpdatePath(RuntimeOs os) => FindHelperFile(GetUpdateExeName(os)); + public static string GetUpdatePath(RID target, ILogger log) => FindHelperFile(GetUpdateExeName(target, log)); public static string GetZstdPath() { @@ -73,7 +83,7 @@ public static class HelperFile } } - private static List _searchPaths = new List(); + private static readonly List _searchPaths = new List(); static HelperFile() { @@ -123,4 +133,4 @@ public static class HelperFile return result; } -} +} \ No newline at end of file diff --git a/src/Velopack.Packaging/PackageBuilder.cs b/src/Velopack.Packaging/PackageBuilder.cs index 983b3b34..d30fdb6c 100644 --- a/src/Velopack.Packaging/PackageBuilder.cs +++ b/src/Velopack.Packaging/PackageBuilder.cs @@ -67,7 +67,6 @@ public abstract class PackageBuilder : ICommand var packId = options.PackId; var packDirectory = options.PackDirectory; var packVersion = options.PackVersion; - var semVer = SemanticVersion.Parse(packVersion); // check that entry exe exists var mainExeName = options.EntryExecutableName ?? options.PackId;