Consuming nuget package containing .targets file via PackageReference - msbuild

I have .NET452 project - lets call it Consumer.csproj that I want to consume nuget lets call it SharedTargets that contained some custom targets files (SharedTargets.targets) from msbuild.
I'm using PackageReference format and now (compared to what it used to be) nuget packages are being restored to shared folder (%userprofile%.nuget\packages), and I'm not sure if it is good idea to reference it via that (doesn't feel right).
Eg:
<PackageReference Include="SharedTargets">
<Version>1.0</Version>
</PackageReference>
<Import
Project="$(USERPROFILE)\.nuget\packages\SharedTargets\1.0\SharedTargets.targets"
/>
Also this works only in VS, running this from command line (msbuild) I'm getting chicken-egg problem:
Confirm that the path in the <Import> declaration is correct, and that
the file exists on disk.
Obviously since I need to restore nuget first before I can use it :)
So question:
is there some more elegant way how to resolve path to the nuget package inside project file
is there a way how to make msbuild succeed (i.e. restore packages before SharedTargets.target is imported)

You shouldn't try to manually import targets distributed via NuGet.
Put your .targets file inside a build subfolder inside the package and name it SharedTargets.targets (package id + .targets) and NuGet will automatically include the targets - for packages.config projects it will modify the project file on install and for PackageReference projects the targets will be imported by modifying an implicitly generated targets file in the obj\ directory.

Related

How do I get the Microsoft.Data.SqlClient.SNI native binaries copied to the bin directory of a web application that targets .NET Framework 4.7.2?

How can I <Import> a .targets file that is included in <PackageReference>d NuGet package in a non-SDK-style web application project that targets .NET 4.7.2?
Background
The web application project is not an SDK-style project. It references a class library project in the same solution. This class library project is an SDK-style project. It has a package reference to Microsoft.Data.SqlClient version 4.1.0, which depends on Microsoft.Data.SqlClient.SNI.runtime 4.0.0. The latter contains native binaries which are automatically copied to the bin directory of the project.
The native binaries are not copied to the bin directory of the web application project. This is probably because of this:
SNI is the native C++ library that SqlClient depends on for various network operations when running on Windows. In .NET Framework applications that are built with the MSBuild Project SDK, native DLLs aren't managed with restore commands. So a ".targets" file is included in the "Microsoft.Data.SqlClient.SNI" NuGet package that defines the necessary "Copy" operations.
The included ".targets" file is auto-referenced when a direct dependency is made to the "Microsoft.Data.SqlClient" library. In scenarios where a transitive (indirect) reference is made, this ".targets" file should be manually referenced to ensure "Copy" operations can execute when necessary.
Recommended Solution: Make sure the ".targets" file is referenced in the application's ".csproj" file to ensure "Copy" operations are executed.
Source
Not sure what "built with the MSBuild Project SDK" means exactly, but I think my scenario qualifies.
Now I'm trying to implement the recommended solution, but I cannot get it to work.
What I did
Step 1: add package reference to Microsoft.Data.SqlClient.SNI version 4.0.0:
<PackageReference Include="Microsoft.Data.SqlClient.SNI">
<GeneratePathProperty>true</GeneratePathProperty>
<Version>4.0.0</Version>
</PackageReference>
I added <GeneratePathProperty> because of step 2 below. According to the documentation this should make a property available that can be used to refer to files in the package:
Sometimes it is desirable to reference files in a package from an MSBuild target. In packages.config based projects, the packages are installed in a folder relative to the project file. However in PackageReference, the packages are consumed from the global-packages folder, which can vary from machine to machine.
To bridge that gap, NuGet introduced a property that points to the location from which the package will be consumed.
Step 2: import the targets file:
<Import Project="$(PkgMicrosoft_Data_SqlClient_SNI)\build\net46\Microsoft.Data.SqlClient.SNI.targets" Condition="Exists('$(PkgMicrosoft_Data_SqlClient_SNI)\build\net46\Microsoft.Data.SqlClient.SNI.targets')" />
You can see that the package contains this targets file using the NuGet package explorer (link). The PkgMicrosoft_Data_SqlClient_SNI property is named according to the documentation (linked above) and based on their example:
MSBuild properties and package identities do not have the same restrictions so the package identity needs to be changed to an MSBuild friendly name, prefixed by the word Pkg.
Step 3: verify the tool versions on our agent:
MSBuild 16.11.2.50704 (installed on the agent C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\bin)
NuGet 6.1.0.106 (installed via a pipeline task)
According to the documentation linked above, these fulfil the minimum requirements (MSBuild 16 and NuGet 5).
Step 4: run nuget restore on our build agent and then build the project using the Azure DevOps MSBuild#1 task with msbuildArguments: '/t:Build'.
Step 4: collect output and publish as an artifact.
Outcome:
The SNI files are not present in the output. When building locally with MSBuild 17.0.0.52104 (installed in 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\bin') I do see the SNI files.

How do I create a NuGet package for a C++/CLI using MSBuild?

I've read these instructions for creating NuGet packages for C++/CLI .vcxproj projects using a .nuspec file. However, I'd like to use MSBuild to create my package instead of using a .nuspec file. I've seen these instructions for packaging .NET Framework projects using MSBuild, but I haven't been able to find anything for doing the same with .vcxproj files.
MSBuild can pack a Visual Studio project into a NuGet package, but only if that project uses PackageReference instead of packages.config.
As of this writing, the Visual Studio UI doesn't allow you to choose PackageReference instead of packages.config as the NuGet package configuration mechanic for VCXPROJ files. However, you can manually edit the .vcxproj file and convert it to PackageReference format.
Edit .vcxproj
By default, .vcxproj files are not SDK-style projects (I don't know if they can even be converted to SDK-style projects). To build NuGet packages from non-SDK-style projects, you'll need to reference NuGet.Build.Tasks.Pack:
Add the following to your .vcxproj (inside the Project element):
<ItemGroup>
<PackageReference Include="NuGet.Build.Tasks.Pack">
<Version>5.11.0</Version>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
Add any other packages your project depends on. Notice the <PrivateAssets>all</PrivateAssets> element. You need NuGet.Build.Tasks.Pack to pack your project, but you don't want it as a dependency when others try and install your NuGet package. Adjust the version for NuGet.Build.Tasks.Pack as appropriate.
If your project references static libraries, you don't want those included as NuGet package dependencies. Use the PrivateAssets element to exclude them. I.e:
<ItemGroup>
<ProjectReference Include="..\vendor\static-lib-build\static-lib\static-lib.vcxproj">
<Project>{8aa99891-3ff7-3fa7-b1b5-131664298a26}</Project>
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
Also add the following inside the Project element"
<PropertyGroup>
<PackageId>Your.Package.Id</PackageId>
<Authors>Your Name</Authors>
<BuildOutputTargetFolder>lib</BuildOutputTargetFolder>
<PackageDescription>A description of this package.</PackageDescription>
<TargetFramework>net461</TargetFramework>
</PropertyGroup>
What's in the TargetFramework element should correspond to what's in the TargetFrameworkVersion element elsewere in your project.
Note that I haven't covered building a cross-platform NuGet package. By its very nature, C++/CLI projects are targeted to a specific processor / architecture. You can include native libraries for multiple architectures in the runtime directory of your NuGet package, but I don't cover that here.
Restore, Build, and Pack Using MSBuild
This line will restore your NuGet packages, build the project, and then pack it:
"c:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\MSBuild.exe" path\to\the_project.vcxproj /p:Platform="Any CPU" /p:ProductVersion=1.0.0.69 /t:Restore;Build /t:pack
Change the command line options to suit your needs. You may need to run this command twice if NuGet.Build.Tasks.Pack hasn't already been restored.

Nuget package content files not served when deployed

I have build NuGet package consisting of Razor Class Library (RCL) project at .net core 2.2. Once packed I see that it contains static assets that I have configured:
When I install package inside target project, assets refs are visible inside project structure:
But when I try to access them at runtime - i get 404:
https://localhost:44312/localizer-assets/lib/vue/vue.min.js
I see that those files are not "physically" placed inside wwwroot folder (they are iniside package folder).
Is there any way to serve those files? How this should be configured?
So far I have been using those approches:
https://www.learnrazorpages.com/advanced/razor-class-library
Can Razor Class Library pack static files (js, css etc) too?
They work fine when I directly reference RCL project, but when packed with NuGet and installed does not work any more.
They work fine when I directly reference RCL project, but when packed
with NuGet and installed does not work any more.
As far as l know, when you use nuget and choose PackageReference nuget management foramt, the files which targets to import into the main project are introduced into your main project as links(The corresponding files will not exist in the physical path of your solution , but will be linked to the corresponding file address in the %userprofile%\.nuget\xxx). It is the feature of PackageReference nuget management format.
Solution
To solve it, l think you can add a custom build target in the nuget project(Include MSBuild props and targets in a package). With it, you can copy the files from nuget package into the main project.
1.add a target file like .targets or .props(must be the same as the package id) into the \build folder(must be created under the project root directory) of the project. All of these are based on nuget packaging mechanism.
2.add these codes into the target file
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<SourceScriptFiles Include="$(MSBuildThisFileDirectory)..\content\xxxx(relative paths in the current project)" />
</ItemGroup>
<Target Name="CopyScriptsToProject" BeforeTargets="Build">
<Copy SourceFiles="#(SourceScriptFiles)" DestinationFolder="$(ProjectDir)\wwwroot\xxxx\"
/>
</Target>
</Project>
Use a target to copy the file #(SourceScriptFiles) into the main project.(The DestinationFolder is just the destination address).
3.If you use nuspec file to pack your package. You should also add these files under the files node in it.
<files>
<!-- Include everything in \build -->
<file src="build\**" target="build" />
<!-- Other files -->
<!-- ... -->
</files>
In addition, such operation is a pre-build event and when you install the package, you should build your project first and then you will find the file under the destination folder.
Besides, here is a good sample in the github and l hope it can give detailed information and steps.
Hope it could help you.

VS2017 msbuild / nuget pack

I am having a problem with nuget (version 4.3.0.4406) and msbuild (version 15.3.409.57025). I am using VS2017 to create class library. Using the pack capability of VS2017 i can successfully create a nuget package (that i can install in another solution). Now i want to add an install.ps1 script to the package in the tools folder that runs when the nuget is installed.
In the csproj file i am specifying multiple target frameworks:
<TargetFrameworks>net45;net452</TargetFrameworks>
I cannot figure out how to do this. I've created a nuspec file using the nuget -spec command which generates a simple nuspec file. When i use the msbuild command with the /t:pack and /p:Nuspecfile=path.to.nuspec I get the following errors:
NuGet.Build.Tasks.Pack.targets(141,5): error : Value cannot be null or an empty string.
I have nuspec files from other projects (from VS2015 solutions) that work without problem, and the structure of the one i am using now is basically the same. Can anyone let me know whether i am trying something that cannot be done?
You can pack any item by updating its metadata in the csproj file:
<ItemGroup>
<None Update="install.ps1" CopyToOutputDirectory="PreserveNewest" Pack="true" PackagePath="\tools" />
</ItemGroup>
Note that the ps1 file is only run for projects using packages.config to reference the NuGet package and you should investigate alternative ways to accomplish what you are trying to do with the script as PackageReference is now more likely to be used instead.

Visual studio 2017 and nuget package

I am creating nuget package which should contain also config file. Problem is that if I want to use csproj for nuget package definition, I don't know how to specify that config file should be copied and "copy to output directory" on file set when package is being installed.
https://learn.microsoft.com/en-us/nuget/guides/create-net-standard-packages-vs2017
Previously I used nuspec and for install.ps1.
Thanks
(https://learn.microsoft.com/en-us/nuget/guides/create-net-standard-packages-vs2017)
To include a file, you need to add / modify an update item for the file. If you have set "copy to output directory" to "preserve newest", you might already have one in the csproj file. To include this file into the resulting nuget package, you need to set the Pack="true" metadata:
<ItemGroup>
<None Update="config.txt" CopyToOutputDirectory="PreserveNewest" Pack="true" />
</ItemGroup>
Note that depending on your project type (=> web sdk or "normal" sdk is being used) you may see different item types (None, Content) for files.