I have a netstandard2.0 csproj (let's call it MyPackage) that is packed at build time (as specified by GeneratePackageOnBuild) into a nuget package. This nuget package has custom props and targets in the build directory (so referencing projects get these imported).
I have another project (let's call it MyConsumer) in the same solution for testing MyPackage. I want MyConsumer to have the build asset props and targets imported from MyPackage at build time, just as if it were consuming it as a PackageReference from some remote nuget source.
How can I get this working (most simply)?
I have been able to do it via a very convoluted method where I have MyConsumer add a PackageReference to MyPackage and override the RestoreSources in MyConsumer to point to the bin directory of MyPackage. This gets very weird when running dotnet build or Visual Studio build of the sln, because project metadata is generated upfront for all projects during Restore and thus MyPackage doesn't exist at that point. The resolution was to add nested calls to MSBuild within the MyConsumer project, but then this becomes even worse, since Visual Studio restores operate quite differently than that automatic restores performed by dotnet build.
Is there any simple way of doing this?
This is what I have now
<Project>
<Target Name="Build">
<Message Text="Running inner build" Importance="high" />
<!--
Need to call MSBuild twice, once to restore, then again to restore and build to get the restore of the Sdk to work
because of this bug in MSBuild: https://github.com/Microsoft/msbuild/issues/2455
Note the trailing Prop=1 is required to get MSBuild to invalid it's cache of the project target imports
-->
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="Restore" Properties="Configuration=$(Configuration);Version=$(Version);IsInnerBuild=true;Prop=1" />
<!-- Have to use dotnet build instead of another call to MSBuild because of another bug that prevents proper imports within the same physical process -->
<Exec Command="dotnet build /p:Configuration=$(Configuration) /p:Version=$(Version) /p:IsInnerBuild=true" />
<Message Text="Finished inner build" Importance="high" />
</Target>
<Target Name="Restore" />
<Target Name="RemoveBin">
<RemoveDir Directories="bin" />
</Target>
<!-- Don't do real cleans old rebuild since it breaks MSBuild due to the same above bug -->
<Target Name="Rebuild" DependsOnTargets="RemoveBin;Build">
</Target>
</Project>
Treat ProjectReference as PackageReference or allow PackageReference to local csproj
If I understand you correct, you want to generate the package with project MyPackage, then install it to the test project MyConsumer and have the build asset props and targets imported from MyPackage at build time.
To accomplish this goal, you need to complete the following few things:
Make sure the project MyPackage build before the project MyConsumer.
Set the package into the packager source
Add the package MyPackage.nupkg to the test project MyConsumer during the build time.
Details for above:
Make sure the project MyPackage build before the project MyConsumer.
Since you wan to test the package which generated by the project MyConsumer, you should make sure this package grnerate before test project using it, so we need set the project MyConsumer reference the the project MyPackage.
Set the package into the packager source
You can use a post-build event for the project MyPackage to copy the package MyPackage.nupkg to the local feed, or you can just add the bin directory of MyPackage.nupkg to the package source.
Add the package MyPackage.nupkg to the test project MyConsumer during the build time.
Using VS 2017 and the PackageReference style of the test project MyConsumer, you can set a Directory.Build.props file into the root of your solution containing the test project MyConsumer you need:
<Project>
<ItemGroup>
<PackageReference Include="MyPackage" Version="1.0.* />
</ItemGroup>
</Project>
This will add these NuGet packages to the test project MyConsumer in the solution, it will be used as a PackageReference from some remote nuget source.
Check the Martin`s answer for some more details.
Hope this helps.
Related
I have a dependency on the NuGet library:
<ItemGroup>
<PackageReference Include="OpenCvSharp4.Windows" Version="4.5.3.20210817" />
</ItemGroup>
This package adds the file opencv_videoio_ffmpeg453_64.dll to the bin folder, which makes some problems. I don't use FFmpeg and want to remove that file (OpenCV said that it is safe to remove it if you don't use FFmpeg backend).
So, I added these lines to the csproj file:
<Target Name="RemoveOpenCvFFmpeg" AfterTargets="AfterBuild">
<Delete Files="$(OutDir)opencv_videoio_ffmpeg453_64.dll" />
</Target>
It works and everything is fine. But when I pack my project as a NuGet package, the consumers of our library still get that file in their bin folder and it also makes them some issues.
Is there any way to delete this file from the local build and from our package in some way, so our consumer won't get this file either?
Or pack them so the consumer will not restore OpenCvSharp4.Windows by its own (I use msbuild /t:pack for packing)
There are some files residing in other directories that, I would like to copy to project folder automatically before build and publishing.
After some research and experimentation, I have come up with the following .csproj file.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.4</RuntimeFrameworkVersion>
<TieredCompilation>true</TieredCompilation>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>
<ItemGroup>
<APIDefinition Include="D:\SomePlace\*.API.*.yaml" />
</ItemGroup>
<Target Name="CopyFiles" BeforeTargets="Compile;Build;Publish">
<Copy SourceFiles="#(APIDefinition)" DestinationFolder="wwwroot" />
<Copy SourceFiles="D:\SomePlaceElse\BaseAPISettings.json" DestinationFolder="$(MSBuildProjectDirectory)" />
</Target>
<ItemGroup>
<Compile Remove="wwwroot\**\*;node_modules;bower_components" />
<None Update="**.user;**.vspscc">
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.4" />
</ItemGroup>
</Project>
Here I have defined CopyFiles target, which should be run before the targets I've placed there. This target uses Copy task to copy YAML format API definition files and base API settings to the project directory.
This works well during build, publish etc. Also, if I delete the local file in the IDE, it instantly recopies it from the source.
Sometimes I make changes to these files between debugging sessions. Then, when I start debugging from Visual Studio, since the project files aren't changed, obviously the already built project is run.
Since the project is not built, my copy tasks are not triggered, and I end up with stale files during debuging.
Is there anything I can do to have my Copy tasks triggered, when I do "Start Debugging F5" in the IDE, regardless of the project build state ?
P.S. : I'm using Visual Studio 2017 15.8.5 and targeting .NET Core 2.1.4 runtime, if it makes any difference.
To integrate fully into the up-to-date check of the project system inside Visual Studio, I susggest the following changes:
Make the items' source and target paths known before
Register them to the up-to-date check system. (Also needs a hack to make sure the project source code is recompiled so that the output will have a newer time stamp)
Make the MSBuild target itself incremental. This also helps for command-line builds when the files don't have to be copied.
The complete changes look like this:
<ItemGroup>
<CustomCopyFile Include="..\TestFiles\*.API.*.yaml"
TargetPath="wwwroot\%(Filename)%(Extension)" />
<CustomCopyFile Include="..\TestFiles\BaseAPISettings.json"
TargetPath="%(Filename)%(Extension)" />
<UpToDateCheckInput Include="#(CustomCopyFile)" />
<UpToDateCheckBuild Include="#(CustomCopyFile->'%(TargetPath)')"
Original="#(CustomCopyFile)" />
<CustomAdditionalCompileInputs Include="#(CustomCopyFile->'%(TargetPath)')" />
</ItemGroup>
<Target Name="CopyFiles"
BeforeTargets="BeforeBuild;BeforePublish"
Inputs="#(CustomCopyFile)"
Outputs="#(CustomCopyFile->'%(TargetPath)')">
<Copy SourceFiles="#(CustomCopyFile)"
DestinationFiles="#(CustomCopyFile->'%(TargetPath)')" />
</Target>
CustomCopyFile now collects all the source files and we put the expected destination file name into the TargetPath metadata.
UpToDateCheckInput items tell Visual Studio to rebuild the project if one of these items change.
UpToDateCheckBuild items instruct Visual Studio to only check these items against special source items. This is redundant for this example project but may be helpful if the target path wasn't inside the project directory but some intermediate output (obj..) folder and no re-evaluation would see these new files. It would also be helpful if the files were also modified as part of processing (e.g. replacing variables inside the files).
CustomAdditionalCompileInputs is a hack here since the items are copied to the project folder and are considered to be "inputs to the output" automatically.. So we force the project to recompile if our source files change. If we don't do so, it would never consider the project up-to-date after a change to the source yaml files since they would be newer than the compiled app.dll file.
New msbuild csproj format have got integrated nuget commands. It's possible to change default path where project assets will be restored by using <RestoreOutputPath>obj\profile7</RestoreOutputPath> command in project file.
But if I add <RestoreOutputPath>obj\profile7</RestoreOutputPath> to csproj file consequent commands
dotnet restore myproj.sln
dotnet build myproj.sln
produce build errors
obj\project.assets.json' not found. Run a NuGet package restore to generate this file.
How to tell MSBuild to get nuget assets from this obj\Profile7 path during the build command?
The restore output path needs to be the same as MSBuildProjectExtensionsPath so that the nuget generated props and targets files will be imported by the common props and targets. as well as BaseIntermediateOutputPath will be the default for composing the path to ProjectAssetsFile.
At least for the NuGet imports, it is important that MSBuildProjectExtensionsPath or BaseIntermediateOutputPath is set before the SDK props file is imported.
The simplest way to solve all of these issues is to set BaseIntermediateOutputPath very early in the project so that all components will take its value as a default base path - this is essentially redirecting obj to somewhere else.
This conflicts with the <Project SDK="..."> syntax since there is no way to set properties before the SDK's props file. To work around this, the project can be changed like this:
<Project>
<!-- This needs to be set before Sdk.props -->
<PropertyGroup>
<BaseIntermediateOutputPath>obj\SomeSubDir\</BaseIntermediateOutputPath>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<!-- other content -->
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>
An alternative would be to create a Directory.Build.props file that will be automatically imported early enough, but this would apply the value to all projects in the directory and take away the ability to specify the value per project.
I have a Visual Studio 2017 solution that contains two projects:
Foo.csproj
Foo.Core.csproj
Both of these projects target multiple frameworks: net452;netstandard1.2
Foo.csproj includes a project reference to Foo.Core.csproj:
<ItemGroup>
<ProjectReference Include="..\Foo.Core\Foo.Core.csproj" />
</ItemGroup>
When I generate a NuGet package for Foo.csproj, I want the nupkg file to include both of these assemblies.
What is currently happening is that the NuGet package that gets created has Foo.dll and then a NuGet dependency on Foo.Core (which doesn't exist).
How can I generate a single NuGet package using msbuild that will include both assemblies?
For reference this is the command I am currently using (which is not working how I want it to):
msbuild /p:restore,pack Foo.csproj
This is currently not directly supported by NuGet out of the box. You can follow this GitHub issue for updates.
However, there are a few ways to create such NuGet package.
Use the "Nugetizer 3000"
This is an newly developed tool to build NuGet packages from projects and works by installing the NuGet.Build.Packaging nuget package. You can find some documentation on it on its GitHub wiki page but since it is a very new project, there isn't much documentation or community knowledge around it yet(!) (but the team developing it is very helpful, you could file GitHub issues if you get stuck).
Adding a custom target in the project (2.0.0 tooling / VS 2017 15.3+): Create an item in the csproj that will include the referenced project's output DLL
This approach is very hacky as it relies on an internal MSBuild item that the pack targets use. It works by first marking the <ProjectReference> to not be referenced from the created nuget package like this:
<ProjectReference Include="..\libA\libA.csproj" PrivateAssets="All"/>
Then you can add this to the project to include the generated libA.dll in the nuget package:
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeP2PAssets</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="IncludeP2PAssets">
<ItemGroup>
<BuildOutputInPackage Include="$(OutputPath)\testprivatelib.dll" />
</ItemGroup>
</Target>
Note that this requires you to add all the <PackageReference> items of the referenced project to the project you generate the package from since they would be missing from the generated package since you effectively disabled the transitive reference behaviour.
Create a custom .nuspec file
At the time of writing, this is probably the most "supported" way, but also the most complex. NuGet allows you to disable the automatic generation of the resulting .nuspec file and automatic collection of files by setting the <NuspecFile> property in your project, along with a <NuspecProperties> property that allows you to pass replacement tokens for parsing the .nuspec file.
This works by modifying the project file like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<NuspecFile>$(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec</NuspecFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LibB\LibB.csproj" />
</ItemGroup>
<Target Name="SetNuspecProperties" BeforeTargets="GenerateNuspec">
<PropertyGroup>
<NuspecProperties>$(NuspecProperties);id=$(AssemblyName)</NuspecProperties>
<NuspecProperties>$(NuspecProperties);config=$(Configuration)</NuspecProperties>
<NuspecProperties>$(NuspecProperties);version=$(PackageVersion)</NuspecProperties>
<NuspecProperties>$(NuspecProperties);description=$(Description)</NuspecProperties>
<NuspecProperties>$(NuspecProperties);authors=$(Authors)</NuspecProperties>
</PropertyGroup>
</Target>
</Project>
This will automatically look for a .nuspec file with the same name as the project (somelib.csproj => somelib.nuspec) and pass some properties along to it. The properties are created in a target in order to be able to access fully resolved and defaulted properties like PackageVersion.
The .nuspec file could look like this:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>$id$</id>
<version>$version$</version>
<authors>$authors$</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>$description$</description>
<dependencies>
<group targetFramework=".NETStandard1.4">
<dependency id="NETStandard.Library" version="1.6.1" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
<files>
<file src="bin\$config$\netstandard1.4\*.dll" target="lib\netstandard1.4\" />
</files>
</package>
Note that you must add all referenced NuGet packages as a <dependency> element in the .nuspec file since these are no longer automatically generated from the <PackageReference> items in your project file. Refer to the NuSpec Reference for more details.
I have recently created an example project on GitHub demonstrating the use of a custom .nuspec file for exactly this purpose.
The second option that Martin Ullrich mentioned is the only one that works out of the box with .NET Standard that allows to "Generate NuGet package on build" as an integral part of the build.
However like he mentions it has a "hard coded" dependency on a dll with an exact name that you expect to be there (on the output folder) which might bite you in the future. I've found a better alternative which worked for me in .NET Standard without the need of any other modification on this post.
I'll quote it here for completeness.
First you edit your csproj and define the PrivateAssets tag for the reference that you'd like to include:
<ItemGroup>
<ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
Then you add this to your csproj:
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
<ItemGroup>
<BuildOutputInPackage Include="#(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'all'))" />
</ItemGroup>
</Target>
That post also shows how to include the PDBs in the NuGet package option if necessary (which I omitted here).
Been struggling with the same issue and none of the suggested workarounds worked (https://github.com/NuGet/Home/issues/3891) and I couldn't change the csproj to use the new SDK coming with .netcore.
Luckily the nuget pack command comes with the -IncludeReferencedProjects option (ref: https://learn.microsoft.com/en-us/nuget/tools/cli-ref-pack) which does exactly that:
"Indicates that the built package should include referenced projects either as dependencies or as part of the package. If a referenced project has a corresponding .nuspec file that has the same name as the project, then that referenced project is added as a dependency. Otherwise, the referenced project is added as part of the package."
Regardless of the *.nuspec file (not needed here) , add -IncludeReferencedProjects to the pack command and the referenced project dlls will be included along with the nuget dll.
nuget.exe pack yourProject.csproj -IncludeReferencedProjects
I have recently discovered that you CANNOT set defaults for the Nuspec Properties you want to replace in the msbuild command line e.g. if a metadata value is set in the .csproj file of "<Version>2.0.0</Version>" and you run:
msbuild myproject.csproj -t:pack -p:Configuration=Release -p:NuspecProperties=Configuration=Release;PackageVersion=1.2.3
Your .nupgk file will have the version 2.0.0 still. Annoyingly the MS documentation is not clear on this and no error is displayed.
When installing Umbraco as a NuGet package only the umbraco folder is included into the Visual Studio project, but not the umbraco_client folder. The reason for this is that the contents of the umbraco_client folder shouldn't be referenced from Visual Studio. But when publishing the website with Web Deploy I need to have the umbraco_client folder deployed.
One solution is to include the umbraco_client folder into the VS project. The downside to this solution is that upgrading Umbraco via NuGet gets annoying and error prone. You have to remember to exclude and re-include the umbraco_client folder into the project.
So is there a better approach?
You can add the following MSBuild script to the .csproj file.
<Project>
...
<PropertyGroup>
<CopyAllFilesToSingleFolderForPackageDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForPackageDependsOn>
</PropertyGroup>
<Target Name="CustomCollectFiles"]]>
<ItemGroup>
<_umbraco_client_files Include="umbraco_client\**\*" />
<FilesForPackagingFromProject Include="%(_umbraco_client_files.Identity)"]]>
<DestinationRelativePath>umbraco_client\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
</Project>
If you want to automate the process of adding the above code into all new Umbraco projects you can create your own NuGet package. Firstly have Umbraco as a dependency. Then add a Build folder and then a .targets file. In the .targets file you add the above code.
For details on the MSBuild script see this article: http://blog.samstephens.co.nz/2010-10-18/msbuild-including-extra-files-multiple-builds/