From cfcd153f93d4d679b89a05b3dc15f8bd9e68a3dd Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Thu, 12 Nov 2026 16:41:57 -0800 Subject: [PATCH] Publish the experimental .NET packages Build the packages' native libraries for linux-x64 and win-x64 in the release workflow, pack both into each package, and check that both are present. Release branches push the packages to NuGet with trusted publishing. Main attaches them to the dev-latest release. Release packages take hyperlight-wasm's version with the preview suffix. Builds from main are versioned as development builds of the next minor release, such as 0.17.0-dev.412. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .github/workflows/CreateRelease.yml | 103 +++++++++++++++++- .gitignore | 1 + CHANGELOG.md | 2 +- Justfile | 16 +++ dotnet/README.md | 4 +- .../Hyperlight.Wasm.Build.csproj | 11 +- .../Hyperlight.Wasm/Hyperlight.Wasm.csproj | 13 ++- 7 files changed, 139 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index 6eaf00ee..176e7c93 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -7,17 +7,89 @@ on: permissions: contents: write - id-token: write # Required for crates.io trusted publishing + id-token: write # Required for crates.io and NuGet trusted publishing jobs: benchmarks: uses: ./.github/workflows/Benchmarks.yml + # Builds the native libraries of the experimental .NET packages. + dotnet-native: + strategy: + matrix: + rid: [linux-x64, win-x64] + runs-on: ${{ matrix.rid == 'win-x64' && fromJSON(format('["self-hosted", "Windows", "X64", "1ES.Pool=hld-win2025-amd", "JobId=dotnet-native-{0}-{1}-{2}"]', github.run_id, github.run_number, github.run_attempt)) || 'ubuntu-24.04' }} + steps: + - uses: actions/checkout@v5 + - name: Hyperlight setup + uses: hyperlight-dev/ci-setup-workflow@v2.3.1 + with: + rust-toolchain: "1.94" + just-version: 1.59.0 + - name: Build native libraries + run: just dotnet-native + - name: Upload native libraries + uses: actions/upload-artifact@v4 + with: + name: dotnet-native-${{ matrix.rid }} + path: dotnet/native/ + if-no-files-found: error + + # Packs the experimental .NET packages with every native library. + dotnet-pack: + needs: [ dotnet-native ] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: extractions/setup-just@v3 + with: + just-version: "1.59.0" + - uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0 + with: + dotnet-version: 8.0.x + - name: Download native libraries + uses: actions/download-artifact@v5 + with: + pattern: dotnet-native-* + merge-multiple: true + path: dotnet/native + - name: Pack + # Release packages take hyperlight-wasm's version with the preview suffix from + # Directory.Build.props. Other builds are development builds of the next minor release. + run: | + version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "hyperlight-wasm") | .version') + if [[ "${{ github.ref }}" == refs/heads/release/* ]]; then + just dotnet-pack-prebuilt "$version" + else + IFS=. read -r major minor _ <<< "$version" + just dotnet-pack-prebuilt "$major.$((minor + 1)).0" "dev.${{ github.run_number }}" + fi + env: + # Reading metadata needs no particular toolchain. + RUSTUP_TOOLCHAIN: stable + shell: bash + - name: Check native libraries in packages + run: | + check() { + unzip -l $1 | grep -qF "$2" || { echo "::error::$1 is missing $2"; exit 1; } + } + check "dotnet/artifacts/Hyperlight.Wasm.[0-9]*.nupkg" runtimes/linux-x64/native/libhyperlight_wasm_capi.so + check "dotnet/artifacts/Hyperlight.Wasm.[0-9]*.nupkg" runtimes/win-x64/native/hyperlight_wasm_capi.dll + check "dotnet/artifacts/Hyperlight.Wasm.Build.*.nupkg" runtimes/linux-x64/native/libhyperlight_wasm_csharp_bindgen.so + check "dotnet/artifacts/Hyperlight.Wasm.Build.*.nupkg" runtimes/win-x64/native/hyperlight_wasm_csharp_bindgen.dll + shell: bash + - name: Upload packages + uses: actions/upload-artifact@v4 + with: + name: dotnet-packages + path: dotnet/artifacts/*.nupkg + if-no-files-found: error + publish: # see https://github.com/orgs/community/discussions/26286#discussioncomment-3251208 for why we need to check the ref if: ${{ contains(github.ref, 'refs/heads/release/') }} || ${{ github.ref=='refs/heads/main' }} - needs: [ benchmarks ] + needs: [ benchmarks, dotnet-pack ] # LLVM 18 has no apt.llvm.org packages for Ubuntu 26.04. runs-on: ubuntu-24.04 steps: @@ -47,6 +119,11 @@ jobs: for BENCHMARK in benchmarks_*; do tar -zcvf $BENCHMARK.tar.gz $BENCHMARK done + - name: Download .NET packages + uses: actions/download-artifact@v5 + with: + name: dotnet-packages + path: dotnet-packages - name: Create pre-release if: ${{ github.ref=='refs/heads/main' }} run: | @@ -54,7 +131,7 @@ jobs: ls -alR gh release delete dev-latest -y --cleanup-tag || true gh release create dev-latest -t "Latest Development Build From Dev Branch" --latest=false -p \ - benchmarks_*.tar.gz + benchmarks_*.tar.gz dotnet-packages/*.nupkg env: GH_TOKEN: ${{ github.token }} shell: bash @@ -64,7 +141,7 @@ jobs: echo "PWD: $PWD" ls -alR gh release create v${{ env.HYPERLIGHTWASM_VERSION }} -t "Release v${{ env.HYPERLIGHTWASM_VERSION }}" --generate-notes \ - benchmarks_*.tar.gz + benchmarks_*.tar.gz dotnet-packages/*.nupkg env: GH_TOKEN: ${{ github.token }} shell: bash @@ -98,3 +175,21 @@ jobs: env: CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }} shell: bash + + - name: Set up .NET + if: ${{ contains(github.ref, 'refs/heads/release/') }} + uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0 + with: + dotnet-version: 8.0.x + - name: Authenticate with NuGet + if: ${{ contains(github.ref, 'refs/heads/release/') }} + uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1 + id: nuget-login + with: + user: ${{ vars.NUGET_USER }} + - name: Publish to NuGet + if: ${{ contains(github.ref, 'refs/heads/release/') }} + run: dotnet nuget push "dotnet-packages/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY" --skip-duplicate + env: + NUGET_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }} + shell: bash diff --git a/.gitignore b/.gitignore index 33a8408d..b6d5f0ed 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ BenchmarkDotNet.Artifacts/ project.lock.json project.fragment.lock.json artifacts/ +dotnet/native/ # Tye .tye/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ea3459a4..912d3cae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). * Add `SandboxBuilder::with_interrupt_vcpu_sigrtmin_offset` to change the signal that interrupts a guest on Linux. * Experimental: add the `experimental_runtime_file` feature, with `SandboxBuilder::with_runtime_file` to load the guest runtime from a file. Snapshots of such a sandbox cannot be saved. * Experimental: add the `hyperlight-wasm-capi` crate, a C API that calls component functions with arguments and results in Hyperlight's component wire format. It is not published. -* Experimental: add the `Hyperlight.Wasm` and `Hyperlight.Wasm.Build` .NET packages, which run components from .NET with typed C# bindings generated from WIT at build time. See `dotnet/README.md`. They are not published. +* Experimental: add the `Hyperlight.Wasm` and `Hyperlight.Wasm.Build` .NET packages, which run components from .NET with typed C# bindings generated from WIT at build time. See `dotnet/README.md`. They are published to NuGet as prereleases for linux-x64 and win-x64. * Experimental: add `hyperlight-wasm-aot experimental-build-runtime`, which builds a guest runtime for a WIT world. ### Changed diff --git a/Justfile b/Justfile index bf8602eb..2f376364 100644 --- a/Justfile +++ b/Justfile @@ -205,6 +205,22 @@ dotnet-pack target="release": dotnet pack dotnet/src/Hyperlight.Wasm -c {{ if target == "debug" {"Debug"} else {"Release"} }} -o dotnet/artifacts dotnet pack dotnet/src/Hyperlight.Wasm.Build -c {{ if target == "debug" {"Debug"} else {"Release"} }} -o dotnet/artifacts +dotnet-rid := (if os() == "windows" { "win" } else if os() == "macos" { "osx" } else { "linux" }) + (if arch == "x86_64" { "-x64" } else { "-arm64" }) +native-lib-prefix := if os() == "windows" { "" } else { "lib" } +native-lib-suffix := if os() == "windows" { ".dll" } else if os() == "macos" { ".dylib" } else { ".so" } + +# Builds the .NET packages' native libraries in release and copies them to dotnet/native/runtimes//native +dotnet-native: + cargo build -p hyperlight-wasm-capi -p hyperlight-wasm-csharp-bindgen --release + mkdir {{ mkdir-arg }} dotnet/native/runtimes/{{ dotnet-rid }}/native + {{ if os() == "windows" { "Copy-Item -Force" } else { "cp" } }} target/release/{{ native-lib-prefix }}hyperlight_wasm_capi{{ native-lib-suffix }} dotnet/native/runtimes/{{ dotnet-rid }}/native/ + {{ if os() == "windows" { "Copy-Item -Force" } else { "cp" } }} target/release/{{ native-lib-prefix }}hyperlight_wasm_csharp_bindgen{{ native-lib-suffix }} dotnet/native/runtimes/{{ dotnet-rid }}/native/ + +# Packs the .NET packages with every runtime in dotnet/native/runtimes into dotnet/artifacts +dotnet-pack-prebuilt version="" suffix="": + dotnet pack dotnet/src/Hyperlight.Wasm -c Release -o dotnet/artifacts -p:HyperlightNativeRuntimesDir={{ justfile_directory() }}/dotnet/native/runtimes/ {{ if version == "" {""} else {"-p:VersionPrefix=" + version} }} {{ if suffix == "" {""} else {"-p:VersionSuffix=" + suffix} }} + dotnet pack dotnet/src/Hyperlight.Wasm.Build -c Release -o dotnet/artifacts -p:HyperlightNativeRuntimesDir={{ justfile_directory() }}/dotnet/native/runtimes/ {{ if version == "" {""} else {"-p:VersionPrefix=" + version} }} {{ if suffix == "" {""} else {"-p:VersionSuffix=" + suffix} }} + # Test a component and a module compiled with pulley examples-pulley target=default-target features="": (build-pulley-rust-component-examples target features) (build-pulley-rust-wasm-examples target features) (run-examples-pulley target features) diff --git a/dotnet/README.md b/dotnet/README.md index 6b0a52d0..bf735d32 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -8,7 +8,7 @@ Run WebAssembly components in Hyperlight micro VMs from .NET, with typed binding * `Hyperlight.Wasm` is the runtime library. It wraps the native `hyperlight-wasm-capi` library. * `Hyperlight.Wasm.Build` generates C# from WIT at build time. It needs no Rust toolchain. -Both target .NET 8 or later. The packages hold native libraries for the runtime identifier they were packed on. +Both target .NET 8 or later. The published packages hold native libraries for linux-x64 and win-x64. ## Usage @@ -90,7 +90,7 @@ Hyperlight runs a helper process, `hyperlight_surrogate_.exe`, for each sa ```sh just dotnet-test # builds the native libraries, a test runtime and component, then runs the tests -just dotnet-pack # packs both packages into dotnet/artifacts +just dotnet-pack # packs both packages for this machine's runtime identifier into dotnet/artifacts ``` `HyperlightNativeDir` overrides where the projects find the native libraries. diff --git a/dotnet/src/Hyperlight.Wasm.Build/Hyperlight.Wasm.Build.csproj b/dotnet/src/Hyperlight.Wasm.Build/Hyperlight.Wasm.Build.csproj index 75bb239b..2cd674e1 100644 --- a/dotnet/src/Hyperlight.Wasm.Build/Hyperlight.Wasm.Build.csproj +++ b/dotnet/src/Hyperlight.Wasm.Build/Hyperlight.Wasm.Build.csproj @@ -22,13 +22,22 @@ + + + + + + - + diff --git a/dotnet/src/Hyperlight.Wasm/Hyperlight.Wasm.csproj b/dotnet/src/Hyperlight.Wasm/Hyperlight.Wasm.csproj index 1bfe9890..224c05b3 100644 --- a/dotnet/src/Hyperlight.Wasm/Hyperlight.Wasm.csproj +++ b/dotnet/src/Hyperlight.Wasm/Hyperlight.Wasm.csproj @@ -12,19 +12,26 @@ - + $(HyperlightNativeDir)$(HyperlightNativePrefix)hyperlight_wasm_capi$(HyperlightNativeSuffix) - + + + + - +