Skip to content
package

GitHub Action

Build and Package .NET Library with Rust Native Binaries

v1 Latest version

Build and Package .NET Library with Rust Native Binaries

package

Build and Package .NET Library with Rust Native Binaries

Builds and packages a .NET library, incorporating pre-downloaded native binaries for specified targets

Installation

Copy and paste the following snippet into your .yml file.

              

- name: Build and Package .NET Library with Rust Native Binaries

uses: Reloaded-Project/devops-rust-c-library-to-dotnet@v1

Learn more about this action in Reloaded-Project/devops-rust-c-library-to-dotnet

Choose a version

reloaded Logo

Reloaded Deploy Rust C Library to .NET Wrapper Action

License

The devops-rust-c-library-to-dotnet GitHub Action builds and packages a .NET library that acts as a wrapper for a Rust library with C exports.

It works by taking the artifact outputs of devops-rust-lightweight-binary@v1 action (usually named C-Library-*), and extracting the native (.dll, .so, .dylib) binaries from them.

These are then placed in appropriate runtime folders in the .NET library project, which is then built and packed into a NuGet package.

An Example

Suppose you run the following step in your GitHub workflow:

test-library-build:
  strategy:
    matrix:
      include:
        - os: ubuntu-latest
          target: x86_64-unknown-linux-gnu
          use-pgo: true
          use-cross: false
        - os: ubuntu-latest
          target: i686-unknown-linux-gnu
          use-pgo: true
          use-cross: false
        - os: ubuntu-latest
          target: aarch64-unknown-linux-gnu
          use-pgo: true # x64 host to aarch64 simulated guest via cross
          use-cross: true
        - os: ubuntu-latest
          target: armv7-unknown-linux-gnueabihf
          use-pgo: true # x64 host to armv7 simulated guest via cross
          use-cross: true
        - os: windows-latest
          target: x86_64-pc-windows-msvc
          use-pgo: true
          use-cross: false
        - os: windows-latest
          target: i686-pc-windows-msvc
          use-pgo: true
          use-cross: false
        - os: windows-latest
          target: aarch64-pc-windows-msvc
          use-pgo: false # no virtualization support (proprietary OS)
          use-cross: false
        - os: macos-13 # x86
          target: x86_64-apple-darwin
          use-pgo: true
          use-cross: false
        - os: macos-14 # M1
          target: aarch64-apple-darwin
          use-pgo: true
          use-cross: false
  runs-on: ${{ matrix.os }}
  steps:
    - name: Checkout Test Repository
      uses: actions/checkout@v4
      with:
        repository: Sewer56/prs-rs
        ref: d08599ed5473616f57d57a0966939e1a5dbda9b4
    - name: Test Library Build
      uses: Reloaded-Project/devops-rust-lightweight-binary@v1-test
      with:
        crate-name: prs-rs
        target: ${{ matrix.target }}
        use-pgo: ${{ matrix.use-pgo }}
        use-cross: ${{ matrix.use-cross }}
        features: "c-exports"
        build-library: true
        upload-artifacts: true

This will produce the following artifacts:

  • C-Library-aarch64-apple-darwin-c-exports.zip
  • C-Library-aarch64-pc-windows-msvc-c-exports.zip
  • C-Library-aarch64-unknown-linux-gnu-c-exports.zip
  • C-Library-armv7-unknown-linux-gnueabihf-c-exports.zip
  • C-Library-i686-unknown-linux-gnu-c-exports.zip
  • C-Library-i686-pc-windows-msvc-c-exports.zip
  • C-Library-x86_64-apple-darwin-c-exports.zip
  • C-Library-x86_64-pc-windows-msvc-c-exports.zip
  • C-Library-x86_64-unknown-linux-gnu-c-exports.zip

Running this action with the following configuration:

- name: Build and Package .NET Library
  uses: Reloaded-Project/devops-rust-c-library-to-dotnet@v1
  with:
    csharp-project-path: bindings/csharp
    install-dotnet: true

Will perform the following.

  1. Install .NET, if needed.
  2. Download artifacts and insert them into appropriate subdirectories of bindings/csharp
bindings/csharp/
├── MyLibrary.csproj
├── runtimes/
│   ├── linux-x64/
│   │   └── native/
│   │       └── libmylibrary.so
│   ├── win-x64/
│   │   └── native/
│   │       └── mylibrary.dll
│   └── osx-x64/
│       └── native/
│           └── libmylibrary.dylib
└── // Other C# files and directories
  1. Build & Pack the Project into a NuGet package.
  2. Upload the NuGet package as an artifact, with name specified in nuget-artifact-name.

Setting up a new Repository

This action was made to be used in conjunction with the excellent Cysharp/csbindgen binding generator by Yoshifumi Kawai (neuecc).

Typical usage is as follows, prs-rs will be used as an example.

  1. Set up csbindgen to generate C# bindings for your Rust library.
// build.rs in Rust project
fn main() {
    csbindgen::Builder::default()
        .input_extern_file("src/exports.rs")
        .csharp_dll_name("prs_rs")
        .csharp_class_accessibility("public")
        .csharp_namespace("prs_rs.Net.Sys")
        .generate_csharp_file("bindings/csharp/NativeMethods.g.cs")
        .unwrap();
}
// Ensure DLL is loaded when requested.
class Init
{
    [ModuleInitializer]
    internal static void RegisterImportResolver()
    {
        // .NET Core 3.X and above only!!
        NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, NativeMethods.DllImportResolver);
    }
}

/// <summary/>
public static unsafe partial class NativeMethods
{
    // https://docs.microsoft.com/en-us/dotnet/standard/native-interop/cross-platform
    // Library path will search
    // win => __DllName, __DllName.dll
    // linux, osx => __DllName.so, __DllName.dylib
    internal static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
    {
        if (libraryName == __DllName)
        {
            var dllName = __DllName;
            var path = "runtimes/";
            var extension = "";

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                path += "win-";
                extension = ".dll";
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                path += "osx-";
                extension = ".dylib";
                dllName = "lib" + dllName;
            }
            else
            {
                path += "linux-";
                extension = ".so";
                dllName = "lib" + dllName;
            }

            if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
            {
                path += "x86";
            }
            else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
            {
                path += "x64";
            }
            else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm)
            {
                path += "arm";
            }
            else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
            {
                path += "arm64";
            }

            path += "/native/" + dllName + extension;
            try
            {
                return NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, path), assembly, searchPath);
            }
            catch (DllNotFoundException)
            {
                return NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, dllName + extension));
            }
        }

        return IntPtr.Zero;
    }
}

For more information, read the full documentation for csbindgen.

  1. Use the Example Above to set up a workflow.

Typical Workflow

Once set up, usage is very easy. In fact, it's automatic.

  1. You add 1 or more C exports to your Rust library.
  2. When you next build, csbindgen will automatically update NativeMethods.g.cs.

You're done. Everything else is automated.

Inputs

Input Required Default Description
artifact-prefix No C-Library- Prefix for the artifact names to download
csharp-project-path No bindings/csharp Path to the C# project directory
install-dotnet No false If true, installs the .NET SDK with specified version
dotnet-version No 8.0.x Version of the .NET SDK to install
nuget-artifact-name No NuGet-Package Name for the NuGet artifact

Supported Targets

This action automatically injects the following target platforms:

  • Windows (x86_64, i686, aarch64)
  • Linux (x86_64, i686, aarch64, armv7)
  • macOS (x86_64, aarch64)
  • Android (x86_64, aarch64, armv7, i686)
  • iOS (aarch64, x86_64)

Basically everything upstream .NET CoreCLR supports, and some hypothetical platforms.

Output

The action generates a NuGet package containing the .NET librarys. The NuGet package is uploaded as an artifact with the name specified by the nuget-artifact-name input.

Contributing

Contributions are welcome! If you encounter any issues or have suggestions for improvements, please open an issue or submit a pull request in this repository.

License

This project is licensed under the MIT License. See the LICENSE file for details.