Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NuGet packaging support #28

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

myd7349
Copy link
Contributor

@myd7349 myd7349 commented Mar 29, 2025

This PR builds upon #17, further improving NuGet packaging.

To respect @glopesdev's work in #17, this PR is directly based on @glopesdev's branch. Therefore, when merging, please consider:

  1. Merging Add support for nuget package generation #17 first, after which I will do a rebase;
  2. Merging this PR (since this PR already includes @glopesdev's work).

If possible, option 1 is preferred, and I will handle the rebase.

Main Updates in This PR

  1. Following the discussion in Add support for nuget package generation #17, the package name and project files have been changed to LSL.Net, and NuGet packaging for LSL.Net has been improved.

  2. Added packaging support for liblsl native libraries.

    The benefit of this approach is that users can install the liblsl dynamic library automatically via NuGet, without manually downloading lsl.dll/so/dylib from the liblsl release page and configuring their project to copy them to the output directory. This makes LSL.Net truly cross-platform and supports both .NET Framework and modern .NET.

    The packaging logic for LSL.Net.runtime is mainly in src/LSL.Net.runtime/Directory.Build.targets, which uses MSBuild's DownloadFile to fetch the liblsl dynamic libraries and Unzip to extract them. The _CollectNativeLibs target handles the final packaging. src/LSL.Net.runtime/Directory.Build.targets also includes special handling for .NET Framework, inspired by SkiaSharp.

    When referenced in .NET Framework projects, the following logic ensures lsl.dll is correctly located:

    #if NET35
        static dll()
        {
            var searchPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            if (IntPtr.Size == 8) // 64-bit
                searchPath = Path.Combine(searchPath, "x64");
            else
                searchPath = Path.Combine(searchPath, "x86");
    
            if (Directory.Exists(searchPath))
            {
                Trace.WriteLine($"Search LSL dynamic library in {searchPath}.");
                SetDllDirectory(searchPath);
            }
        }
    
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool SetDllDirectory(string path);
    #endif
  3. Added a new example, LSLVer, for simple testing.

  4. Improved the GitHub Actions workflow.


Discussion Points

There are generally two approaches to packaging native libraries:

Approach 1

Package both the native libraries and the C# binding library into a single NuGet package.

Approach 2

The downside of approach 1 is that the package size becomes very large. Considering all platforms (android, ios, linux, macOS, windows) and architectures (arm, arm64, x64, x86), the liblsl dynamic libraries could total tens of megabytes.

Therefore, this PR follows the packaging strategy of NetVips, SkiaSharp, SQLitePCLRaw, and opencv.net, packaging native libraries separately instead of bundling them with the C# binding library.

Dependency Question:

Should the C# binding library depend on the native library NuGet packages?
For LSL.Net, the question is: Should LSL.Net depend on LSL.Net.runtime.win-x64, LSL.Net.runtime.win-x86, and future packages like LSL.Net.runtime.linux-x64?

This PR follows NetVips' approach (NetVips on NuGet) and does not make LSL.Net depend on LSL.Net.runtime.
However, SkiaSharp (NuGet) does make its binding library depend on native packages (SkiaSharp.NativeAssets.macOS, SkiaSharp.NativeAssets.Win32).

Naming Question:

The native library NuGet packages are named LSL.Net.runtime.[rid]. Do you agree with this naming?
Some reference packages:

  • NetVips: NetVips.Native.win-x64, NetVips.Native.win-x86
  • SkiaSharp: SkiaSharp.NativeAssets.macOS, SkiaSharp.NativeAssets.Win32
  • System.IO.Ports: runtime.native.System.IO.Ports, runtime.osx-x64.runtime.native.System.IO.Ports, runtime.linux-x64.runtime.native.System.IO.Ports

Versioning Issue

I prefer keeping the version number of liblsl-Csharp in sync with liblsl. For example, if the current liblsl version is 1.16.2, then liblsl-Csharp should also be 1.16.2.

Since we haven't officially released it yet and there are still many areas for improvement, we can consider first releasing a preview version: 1.16.2-preview.1.


Unresolved Issues

  1. Linux-x64, OSX-x64, OSX-arm64 packaging not yet added

    • The MSBuild Unzip task does not support .tar.bz2, which is used for liblsl-1.16.2-OSX_amd64.tar.bz2 in the liblsl releases page.
    • This is why these platforms are not yet included in the package.
  2. ProjectReference vs. PackageReference

    • As noted in dotnet/sdk#19929, when example projects reference LSL.Net.runtime via ProjectReference, they cannot locate the liblsl dynamic libraries at runtime.

    • Current workaround:

      • All example projects import src\LSL.Net.runtime\CopyNativeLib.targets, which copies the native libraries to the output directory.
      • However, this still has issues:
        1. On Windows, it does not perfectly handle x86/x64/AnyCPU.
        2. On Linux/macOS, users still need to manually set LD_LIBRARY_PATH / DYLD_LIBRARY_PATH.
    • Alternative solution:

      • First, build the LSL.Net.runtime NuGet package and reference it as a local package in the example projects (guide).
    • Best solution:

      • Once LSL.Net.runtime is published to NuGet, reference it via PackageReference.
      • (For now, SharpLSL.Native.* could be used as a temporary workaround.)

Next Steps

  1. Refactor LSL.cs into multiple files.
  2. Convert comments to Documentation Comments (MS Docs) for API documentation generation.
  3. Add support for more platforms (may require support from the liblsl team).
    • Decide whether to build dynamic libraries inside liblsl or in a separate repo (e.g., liblsl-ci-build).
    • For Windows, consider:
      1. Generating .pdb files for unmanaged code debugging.
      2. Building lsl.dll without MSVC dependency, so users don't need the Microsoft VC++ runtime.
      3. String encoding issues:
        • liblsl does not specify (or at least, I couldn't find) a string encoding standard.
        • On Windows, default encoding is locale-dependent.
        • On Linux, default encoding is usually UTF-8.
        • The encoding used for cf_string should also be clarified.
  4. Adjust API naming to better follow C# naming conventions (mark old APIs as obsolete but keep them temporarily).
  5. Port more liblsl examples to C#.
  6. Add LibraryImport support for AOT compilation.

Many of these improvements are already implemented in SharpLSL, so they should not be too difficult to integrate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants