Adding x86 and x64 libraries to NuGet package
Asked Answered
S

3

29

I have made a library which depends on CefSharp which requires to build the library for specific platforms. So no AnyCPU support.

Now I want to pack this into a NuGet. As far as I understand, you have to put these files into the build folder and have a .targets file which picks the correct dll to reference. So I ended up with a NuGet package looking like this:

lib
    monodroid
        MyLib.dll
    xamarin.ios10
        MyLib.dll
    net45
        MyLib.dll (x86)
build
    net45
        x86
            MyLib.dll (x86)
        x64
            MyLib.dll (x64)
        MyLib.targets

I put the following inside of the .targets file:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="PlatformCheck" BeforeTargets="InjectReference"
    Condition="(('$(Platform)' != 'x86') AND  ('$(Platform)' != 'x64'))">
    <Error  Text="$(MSBuildThisFileName) does not work correctly on '$(Platform)' platform. You need to specify platform (x86 or x64)." />
  </Target>
  
  <Target Name="InjectReference" BeforeTargets="ResolveAssemblyReferences">
    <ItemGroup Condition="'$(Platform)' == 'x86' or '$(Platform)' == 'x64'">
      <Reference Include="MyLib">
        <HintPath>$(MSBuildThisFileDirectory)$(Platform)\MyLib.dll</HintPath>
      </Reference>
    </ItemGroup>
  </Target>
</Project>

So far so good. Now to the problem. When adding this NuGet to a new WPF project, I see the reference to the library appearing in the .csproj file like:

<Reference Include="MyLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d64412599724c860, processorArchitecture=x86">
  <HintPath>..\packages\MyLib.0.0.1\lib\net45\MyLib.dll</HintPath>
  <Private>True</Private>
</Reference>

Although I don't see anything mentioned about the .targets file. Is this still the way to do it with NuGet 3? Did I do something wrong? Currently, this fails at runtime when running x64 because of the reference to the x86 lib.

Sciatica answered 3/2, 2016 at 14:15 Comment(6)
If you do not need to support the newer Windows 10 projects that use project.json I would just try removing the lib\Net45 directory. NuGet should install into a WPF project and just import the .targets file into the project file (.csproj).Shivers
I don't plan to support UWP or any newer projects that use the new project.json stuff. Will have a try again tomorrow. However, still puzzled not to see the targets being referenced in the csproj file where the nuget was added.Sciatica
I tried removing the lib\net45 folder, which did nothing but making it not build at all because it cannot find the dll.Sciatica
Works fine for me on both Windows and Mac. I created a simple example NuGet package with your MSBuild target. I can use that in a C# project and build it with x86 and x64 and the appropriate .dll is referenced.Shivers
I've followed the questions and comments here and the targets file is referenced by my csproj and the build works, however the DLLs I'm adding don't show up in the VS (2015) references. Should they? ThanksMcbryde
No they won't show up in VSSciatica
S
28

After fiddling with this for way too long, I figured it out.

Apparently, the build.prop and build.target files need to have the exact same name as the NuGet package has, otherwise it will not be added in the csproj file.

EDIT:

I have created a small GitHub repository showing how to use this for your own project.

It demonstrates a solution with 3 projects - one for iOS, one for Android, and a Net45 which targets both x86 and x64.

Notice that in the .props file, the paths are pointing at the folder structure of the unpacked NuGet. So these paths map to what you have put in your nuspec file.

So as in the repo, you define a nuspec like:

<?xml version="1.0"?>
<package>
  <metadata>
    <id>My.Awesome.Library</id>
    <version>1.0.0</version>
    <title>My Awesome Library</title>
    <description>Herpa Derpa</description>
  </metadata>
  <files>
    <file src="My.Awesome.Library.Droid\bin\Release\My.Awesome.Library.*"
      target="lib\monodroid70" />

    <file src="My.Awesome.Library.iOS\bin\Release\My.Awesome.Library.*"
      target="lib\xamarin.ios10" />

    <file src="My.Awesome.Library.Net45\bin\x64\Release\My.Awesome.Library.*"
      target="build\x64" />

    <file src="My.Awesome.Library.Net45\bin\x86\Release\My.Awesome.Library.*"
      target="build\x86" />
    <file src="My.Awesome.Library.Net45\bin\x86\Release\My.Awesome.Library.*"
      target="lib\net45" />
    
    <file src="My.Awesome.Library.props" target="build\net45" />
  </files>
</package>

I put my x86 files into build\x86 and x64 files into build\x64. The name of the folder build could essentially be anything in this case, for these files. What matters here is that the props file, below points at these folders for the respective platforms.

I am not sure whether putting the x86 lib into lib\net45 even matters. You can experiment with that. The props file should pick the correct one for you anyways.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Reference Include="My.Awesome.Library" Condition="'$(Platform)' == 'x86'">
      <HintPath>$(MSBuildThisFileDirectory)..\x86\My.Awesome.Library.dll</HintPath>
    </Reference>
    <Reference Include="My.Awesome.Library" Condition="'$(Platform)' == 'x64'">
      <HintPath>$(MSBuildThisFileDirectory)..\x64\My.Awesome.Library.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

In this case, $(MSBuildThisFileDirectory) would be the build\net45\ folder, make sure your path correctly points at the dll's. The build\net45 folder is important here. This is how NuGet automatically imports targets and props files.

Also notice the name My.Awesome.Library is pretty consistent all around. What is important here is that the name of your props file matches the NuGet package ID. Otherwise, it seems like NuGet won't import it.

Sciatica answered 9/2, 2016 at 21:53 Comment(0)
S
13

There are a few things that I disagree with from the solution above.

The nuspec file does not have to have any <Files> information if you use the default file structure by adding the files into a nuget folder. But if you want to run the nuget files from your solution folder then it is needed

better nuspec content:

<?xml version="1.0"?>
<package>
  <metadata>
    <id>YourProjectName</id>
    <version>1.0.0</version>
    <authors>John Doe</authors>
    <title>Your Project Name</title>
    <description>Herpa Derpa</description>
  </metadata>
</package>

I also disagree that he has the .dll in the build folder, these should go in your lib folder and in the build folder should contain your .targets and .props files. The documentation for nuget here mentions this.

Also the lib folder are for .dll's that are in your references. Secondly, .dlls added to your build folder are supplementary dll's that might are needed in some cases-similar to how SQlite does it for it's interop dll. So in this case, the better place to keep them is in the lib folder.

Better folder structure:

- nuget
-- project.nuspec
-- build 
---- YourProjectName.props
---- YourProjectName.targets
-- lib 
--- <x64>
---- YourProjectName.dll
--- <x86>
---- YourProjectName.dll

.props file content:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Reference Include="YourProjectName" Condition="'$(Platform)' == 'x86'">
          <HintPath>$(MSBuildThisFileDirectory)x86\YourProjectName.dll</HintPath>
        </Reference>
        <Reference Include="YourProjectName" Condition="'$(Platform)' == 'x64'">
          <HintPath>$(MSBuildThisFileDirectory)x64\YourProjectName.dll</HintPath>
        </Reference>
      </ItemGroup>
    </Project>

.targets file content:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="PlatformCheck" BeforeTargets="InjectReference"
    Condition="(('$(Platform)' != 'x86') AND  ('$(Platform)' != 'x64'))">
    <Error  Text="$(MSBuildThisFileName) does not work correctly on '$(Platform)' platform. You need to specify platform (x86 or x64)." />
  </Target>
  <Target Name="InjectReference" BeforeTargets="ResolveAssemblyReferences">
    <ItemGroup Condition="('$(Platform)' == 'x86' or '$(Platform)' == 'x64') and '$(TargetFrameworkVersion)'=='v4.6'">
      <Reference Include=" YourProjectName ">
        <HintPath>$(MSBuildThisFileDirectory)..\..\lib\net46\$(Platform)\ YourProjectName.dll</HintPath>
      </Reference>
    </ItemGroup>
  </Target>
</Project>

You can check that the above worked by opening your nuspec file with the Nuget Package Manager APP and checking all the files you added are picked up. Now when you add these to a project it will load the dll according to the architecure selected for the project it was referenced in. Also notice in the targets file it will raise an error if "Any CPU" is selected, and inform the user they must choose between x86 and x64

EDIT:

One point that was not clarified well was the naming of the .target and .props needs to be the same as the nuget package file name (meaning the version as well). As without this the double reference would appear when re-opening Visual Studio.

Sauers answered 19/3, 2018 at 15:21 Comment(11)
do you need both props and target files? what is the difference between the two?Loireatlantique
technically neither are required. They provide optional actions that can be run. Check: learn.microsoft.com/en-us/nuget/create-packages/… at the section "Including MSBuild props and targets in a package"Sauers
the reason I ask is because I tried the above, with just the targets file, but it did not copy my x64 dll to the build directory. It only copied the x86 version for both build configurations.Loireatlantique
Emm a bit confused, you are building with x64 build configuration and its outputtin the x64 dll in a folder named x86? Best post a question with what you have and what its doing and send me a link to have a look OR add the props file and see if it solves your issueSauers
I have the same folder structure as above (except my architecture folders are under the net452 under lib). So after I add the nuget package to a project, I set project to build as x86, then the x86 version of the dll gets copied to x86 build folder (the root of it - not into subfolders) which is correct. But when I switch project to x64, the x86 version of the dll gets copied to the x64 build folder. I was expecting the x64 version that I bundled in the nuget package to get copied to the x64 build folder.Loireatlantique
You must reinstall the nuget package after changing build config. Your expected behavior would only work if the dll was "Any CPU"Sauers
For anyone struggling with this. The props file makes sure that the editor is happy while the targets file ensures a proper build. Just make sure that the relative paths inside both files are 100% correct. I did not need to encode the exact version into the filename My.Awesome.Lib.targets was enough.Irfan
Why are the '<>' surrounding the x86/x64 folder names in your example? Does that have any special meaning? I get a nuget warning during pack when doing this (without '<>'). 'WARNING: NU5103: The folder 'lib\x86\MyLibrary.dll' under 'lib' is not recognized as a valid framework name or a supported culture identifier. Rename it to a valid framework name or culture identifier.'Lynnettelynnworth
@obiwanjacobi, where are you putting <x64>/<x86>? , you shouldnt need to create the folder structure.Sauers
This is where I'm currently at. Continued experimenting after writing the comment. github.com/obiwanjacobi/vst.net/blob/netcore3/Source.Core/Code/…Lynnettelynnworth
@Lynnettelynnworth , well just play around see what works, sorry i dont know... i didnt specify the file structure as i mentioned, its not needed.Sauers
M
-1

To clarify the whole solution this is the way how it workes perfectly for me

.nuspec file

 <?xml version="1.0"?>
<package>
  <metadata>
    <id>YourProjectName</id>
    <version>1.0.0</version>
    <authors>John Doe</authors>
    <title>Your Project Name</title>
    <description>Herpa Derpa</description>
  </metadata>
</package>

Folder Structure

-- project.nuspec
-- build 
---- YourProjectName.props
---- YourProjectName.targets
-- tools
--- <x64>
---- YourProjectName.dll
--- <x86>
---- YourProjectName.dll

.props file

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Reference Include="YourProjectName" Condition="'$(Platform)' == 'x86'">
          <HintPath>$(MSBuildThisFileDirectory)..\tools\x86\YourProjectName.dll</HintPath>
        </Reference>
        <Reference Include="YourProjectName" Condition="'$(Platform)' == 'x64'">
          <HintPath>$(MSBuildThisFileDirectory)..\tools\\x64\YourProjectName.dll</HintPath>
        </Reference>
      </ItemGroup>
    </Project>

.targets file

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="PlatformCheck" BeforeTargets="BuildOnlySettings"
    Condition="(('$(Platform)' != 'x86') AND  ('$(Platform)' != 'x64'))">
    <Error  Text="$(MSBuildThisFileName) does not work correctly on '$(Platform)' platform. You need to specify platform (x86 or x64)." />
  </Target>

</Project>

That's 100% working solution.

Maher answered 12/1, 2019 at 13:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.