PingZing
PingZing

Reputation: 962

Include native library in NuGet package consumed by Xamarin.Android

I have a two-part project. Part one is a set of C# bindings that wrap a native .dll or .so library. This produces a NugGet package, which is then consumed by the other project.

The other is a Xamarin Forms project that consumes those C# bindings. The UWP portion of my Xamarin project has no issue finding the native library inside the NuGet package. The Android project, however, cannot find it. Indeed, opening the APK reveals that the native .so file never makes it in.

I'm using NuGet's mechanism for including arch-specific libraries, by including each platform-specific .dll-or-.so in a /runtimes/platform-arch/native/ folder. My folder structure looks like this for the bindings project:

Folder structure of bindings project

...and I can confirm that the folder structure is properly preserved in the resulting .nupkg file, with a runtimes folder at the root of the package, and a series of subfolders underneath it.

I generate the NuGet package by using the new built in .csproj mechanisms, without any .targets files. My csproj for the NuGet project looks like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <Version>1.0.5.1</Version>
    <Company />
    <Product />
  </PropertyGroup>

  <ItemGroup>
    <Content Include="runtimes\**" PackagePath="runtimes">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="OneOf" Version="2.1.150" />
  </ItemGroup>

</Project>

The second part of my project is a Xamarin Forms project, and I import the NuGet package to the .NET Standard cross-platform portion of the project.

When I build the UWP project, my platform-specific .dll is correctly copied to the root of my .appx file, and I can successfully P/Invoke it. (I have noticed that this only works if I build in an x64 configuration, and I am compiling on an x64 machine.) However, when building the Android project, no such thing happens with the .so Android library.

The entire dependency chain looks something like this:

-------------NuGet Package----------
|   - native_lib.so                |
|   - (other runtime dlls, etc)    |
------------------------------------
                 ^
                 |                 
-----Xamarin.Forms Core Project-----
| - Imports NuGet                  |
------------------------------------
                  ^
                  |
-------Xamarin Android Project------
| - Imports Xamarin Forms Core Proj|
| - Produces APK                   |
------------------------------------

It does not seem to be a naming issue--attempting to rename native_lib.so in the bindings project to libnative_lib.so has no effect.

Am I missing a step, or does NuGet simply not understand that a Xamarin.Android project needs native libraries copied into the APK?

Source code for the NuGet project: https://github.com/pingzing/scannitsharp Source code for the Xamarin Forms project that consumes the NuGet: https://github.com/pingzing/scannit NuGet package: https://www.nuget.org/packages/ScannitSharp/1.0.5.1

Upvotes: 4

Views: 2113

Answers (1)

mfkl
mfkl

Reputation: 2184

Generally speaking you need to share more info in your Q, such as your code and packaging files.

does NuGet simply not understand that a Xamarin.Android project needs native libraries copied into the APK?

Yes it does. You need to package your NuGet in a way that MSBuild includes this file in the final APK.

For Android, this is done with AndroidNativeLibrary

See https://learn.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-process#item-attribute-name

And a working example here https://github.com/mfkl/libvlc-nuget/blob/master/build/VideoLAN.LibVLC.Android.targets

I have noticed that this only works if I build in an x64 configuration, and I am compiling on an x64 machine.

You likely need to modify your targets file to handle the 3 possibles cases AnyCPU, x86 and x64. Your target should run before the actual build, with BeforeTargets="BeforeBuild".

You can use something like

<Content Include="@(WindowsX64IncludeFilesFullPath)">
        <Link>$(WindowsX64TargetDir)\$([MSBuild]::MakeRelative($(MSBuildThisFileDirectory)..\build\x64\, %(FullPath)))</Link>
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Check this out: https://github.com/mfkl/libvlc-nuget/blob/master/build/VideoLAN.LibVLC.Windows.targets

Upvotes: 1

Related Questions