Prevent duplicating files in NuGet content and contentFiles folders - visual-studio

My NuGet package needs to deliver some rather large files to build output directory.
In an old NuGet model, such files have to be stored in content folder of the .nupkg. While in a new model introduced in NuGet 3.3, such files have to be stored in contentFiles folder.
To maintain a compatibility with older versions of NuGet and mainly with Package.config package management format, I need to duplicate the files into both folders. That unfortunately almost doubles a size of the package.
Is there a way to prevent that? Can I somehow link contentFiles to content folder?

Found updated documentation describing this in detail at
MSBuild targets for NuGet.
By default, everything gets added to the root of the content and contentFiles\any\<target_framework> folder within a package and preserves the relative folder structure, unless you specify a package path:
<Content Include="..\win7-x64\libuv.txt">
<Pack>true</Pack>
<PackagePath>content\myfiles\</PackagePath>
</Content>
If you want to copy all your content to only a specific root folder(s) (instead of content and contentFiles both), you can use the MSBuild property ContentTargetFolders, which defaults to "content;contentFiles" but can be set to any other folder names. Note that just specifying "contentFiles" in ContentTargetFolders puts files under contentFiles\any\<target_framework> or contentFiles\<language>\<target_framework> based on buildAction.

If you only want to output the file to the build output (content only copies the file to the output directory but does cause it to be set as copy to output directory item), you can use a completely different approach by creating an msbuild file that will be included in the project.
You can do this by putting both the file - say test.jpg into the tools folder (you could also use build) and add a Your.Package.Id.targets file to the build folder (the name being the package id of your package with .targets as extension) with the following content:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)..\tools\test.jpg">
<Link>test.jpg</Link>
<Visible>false</Visible>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>
This target will be automatically imported into the project files regardless of which "style" of NuGet reference is used (packages.config, PackageReference) and should be backwards compatible to older versions of VS as long as they support NuGet and ToolsVersion 4.0.
The Link metadata denotes where in the output / publish directories the file will end up. You could set it to e.g. defaultContent\images\foo.jpg to create a nested structure and rename the file. (you could even use MSBulid variables to use some of the referencing project's configuration). The Visible metadata prevents the solution explorer from showing the full relative path to the file, which could end up in lots of nested .. nodes. The CopyToPublishDirectory applies to .NET Core / ASP.NET Core apps or SDK-based projects using the Publish target for publishing.
Note that you can set the Inclue-path to anything depending on where in your package the file is. You can also use wildcards (but then set Link to %(Filename)%(Extension))

Related

Create NuGet package that when installed copies content to $(OutputPath) regardless of machine

I am trying to get a content file that is included with my NuGet package to be copied to the $(OutputPath) (in my case bin\Debug\netcoreapp3.1 where all the DLLs end up) of a target C# project when the target is built. This needs to work on developers' boxes and on build machines. Here's what I have so far.
I created a NuGet package project with a content node
<ItemGroup>
<Content Include="testsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
I am using the default values for the pack target inputs IncludeContentInPack and ContentTargetFolders, which are true and content;contentFiles respectively.
When I build the package, I get the following structure where testsettings.json appears in contentFiles\any\netcoreapp3.1 and content folders of the .nupkg.
When I install the package into a target .csproj,
the testsettings.json file is added to the project's root folder as expected
the <PackageReference Include="..." Version="..."/> line is added to the target .csproj as expected
no entry appears in the target .csproj for testsettings.json
When I build the target project, the DLLs go to $(OutputPath) but testsettings.json is not copied there, which makes sense because the default for a Content node's action is None). However, I need it to act like <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>.
I thought, "OK I will manually change testsettings.json using the Properties window to Copy if newer":
This adds a node to the target .csproj that looks like:
<ItemGroup>
<Content Update="C:\redacted\path\to\nuget\cache\package.name\N.N.N\contentFiles\any\netcoreapp3.1\testsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
which works great for my machine. This will fail for any build machine and any other developer's machine because that path is specific to my machine.
How can I create the package so that when installing the package it will result in a repeatable way of getting testsettings.json in the right place?
In the course of researching this, I noticed that testhost.exe and testhost.dll somehow magically end up in the right spot even though their source NuGet package's .nuspec and structure are similar to mine. This has left me at bit of a loss on what I need to do to modify my NuGet package's .nuspec, .nupkg, build sequence, etc.
Preferable: Is there a way to do this, and if so how? Highly not preferable: Is there a manual change I can make in the target project such that I can get a reliable way of copying the file that works everywhere?
<ItemGroup>
<Content Include="XXXXX.json">
<PackageCopyToOutput>true</PackageCopyToOutput>
</Content>
</ItemGroup>
should meet your requirements.
In your NuGet project's xxproj file, add codes like above and use <PackageCopyToOutput>.
In the new project which you'd like to test, before installing your NuGet Package, remember to clear the NuGet Package cache first, and then install your NuGet package and build your project, check if the XXXXX.json file has been copied to Output.
Similar thread: Copy JSON file to bin from class library nuget package.

Specify PackageFlatten for content files in Nuget package using Visual Studio 2017

I'm trying to get content files in a C#.Net project that is being packed into a Nuget package to flatten the containing folders. Based on this page it seems that this is possible with the flatten attribute.
But I want to use the new MSBuild Nuget packaging built into VS2017. Based on this page, I should be able to achieve the same thing with the <PackageFlatten> tag, but I cannot get it to work.
Here is a basic project file which demonstrates the problem. I'm expecting the single content file under the folder hierarchy .\RemoveMe\ContentFile.txt to end up in the Nuget package under .\content\ContentFile.txt - i.e. without the folder RemoveMe. But I still get this subfolder.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<Content Include="RemoveMe\ContentFile.txt">
<PackageFlatten>true</PackageFlatten>
</Content>
</ItemGroup>
</Project>
Specify PackageFlatten for content files in Nuget package using Visual Studio 2017
It should be a issue about argument flatten="true" for me. According to the document NuGet ContentFiles Demystified:
flatten - Should the files be all delivered to the root folder of the project or should they keep the folder structure that they were packaged in. The default value is false, which indicates they should create (if necessary) and keep the same structure they have after the TFM folder in the pacakge.
So if we set this argument flatten="true" in the .nuspec file, then pack this .nuspec, install the generate nuget package to the project, the file should be delivered to the root folder of the project. But after install this package to project, still got the that file in the sub-folder RemoveMe not in the root folder.
I also test other arguments buildAction="content" copyToOutput="false", both of them works fine.
So it should be a issue for the argument flatten="true", I submit this issue on GitHub, you can track this issue.

Making nuget work when its referenced in multiple solutions or by itself?

We have the following project structure in a solution:
.\Project2
.\Project1
Project2 is dependent on Project1. Project1 is shared across multiple projects. I added the nuget packages (Roslyn for example) for Project1 when it was part of this solution. The problem we are having if either we try to compile Project1 by itself or compile it when its in an entirely another app it fails:
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is ../../Project2/packages\Microsoft.Net.Compilers.1.0.0\build\Microsoft.Net.Compilers.props.
If i open the project file in a text editor it has the following statments in the project file:
<Import Project="..\..\Project2\packages\Microsoft.Net.Compilers.1.2.2\build\Microsoft.Net.Compilers.props" Condition="Exists('..\..\Secured Account Access\Member\packages\Microsoft.Net.Compilers.1.2.2\build\Microsoft.Net.Compilers.props')" />
If of course removed all the nuget references and then just opened the individual project and added the nuget references. Which fixed when i was trying to compile the project by itself and broke it when it was part of the solution because VS will no longer restore the packages for Project1 while its in the solution.
I'm not sure why VS/Nuget works this way. Why not always have a package folder for each project? Disk Space savings (which would be absolutely nothing)? You should never have references that look like the following:
../../Project2/Packages/MyNugetDepedency.dll
Is there any way to get around this and have everything just work.
The packages folder is determined by the solution the project is contained in by default. You can override the default packages directory by specifying the repositoryPath in a NuGet.Config file.
<configuration>
<config>
<add key="repositoryPath" value="../MyPackages" />
</config>
</configuration>
To have multiple solutions all share the same packages directory you can create a NuGet.Config file, with a repositoryPath setting, in a directory that is a parent to all the solutions. NuGet will then work its way up each directory until it finds a NuGet.Config file and then use the repositoryPath defined there. This repositoryPath is relative to the NuGet.Config file itself, unless you have specified a full path. A relative path is probably what you want so other developers do not need to check out the source code to the same directory.

Set content files to "copy local : always" in a nuget package

I generate a nuget package from a project with this command in the post-build event. the variable %conf% is set to the right configuration (debug or release) and %1 is the project name (e.g. "MyCompany.MyProject").
nuget pack -Prop Configuration=%conf% "%1.csproj" -exclude *.sql -IncludeReferencedProjects
This package is for our own usage only, it will never be published on nuget. It ends in our private repository.
In the project, there is a file that is set to generate action : content and copy local : always. (My Visual Studio is in French, so I'm not 100% sure of the translation). Let's name it importantfile.xml.
In the generated package, I end up with this structure :
- content
- importantfile.xml
- lib
-net45 (.NetFramework,Version=v4.5)
-MyCompany.MyProject.dll
Which is fine, I want importantfile.xml to be deployed in the package, because, well, this file is important!
When I install the package in another project, importantfile.xml is deployed at the root of the project. That's OK. But it is not set to copy local : always.
I need importantfile.xml to be copy local : always in this project where I install my package.
How can I achieve that?
Notes :
I can set copy local : always on the file just after installing the package, that's no big deal. I would live with it if later updates of the package would let this property as-is, which is not the case. When updating the package, copy local is reset to never (as stated here).
There's a nuspec file in the project's folder, here it is :
<?xml version="1.0"?>
<package >
<metadata>
<id>$id$</id>
<version>$version$</version>
<title>$title$</title>
<authors>$author$</authors>
<owners>$author$</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>$description$</description>
<copyright>Copyright 2014</copyright>
<tags>some random tags</tags>
</metadata>
</package>
Instead of using a PowerShell script another approach is to use an MSBuild targets or props file with the same name as the package id:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)importantfile.xml">
<Link>importantfile.xml</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
In the nuspec file then, instead of adding the required files to the Content directory, add them to the Build directory along with the targets file.
Build
importantfile.xml
MyPackage.targets
lib
net45
MyAssembly.dll
If you require different content for different architectures then you can add architecture folders under Build also each with their own targets file.
Benefits to using a targets file over the PowerShell script with NuGet Content directory:
required content files aren't shown in the project in Visual Studio
content files are linked to rather than copied into the directory of each project which references the NuGet package (preventing there being multiple copies and keeping behaviour the same as for assemblies / libraries from NuGet packages)
PowerShell scripts only work in Visual Studio and aren't run when NuGet is run from the commandline (build servers, other IDEs and other OS), this approach will work everywhere
PowerShell install scripts are not supported in NuGet 3.x project.json system.
I know you guys got a working solution to this but it didn't work for me so I'm going to share what I pulled out of the NLog.config NuGet package install.ps1 (github source here).
NOTE: this is not my code, this is the content of the install.ps1 from
the NLog.config nuget package just sharing the knowledge.
It seems a little more straight forward to me and just hoping to help others that will likely stumble upon this.
You can find the accepted int values for BuildAction here and the accepted values for CopyToOutputDirectory here.
if the link breaks again
Fields
prjBuildActionCompile 1
The file is compiled.
prjBuildActionContent 2
The file is included in the Content project output group (see Deploying Applications, Services, and Components)
prjBuildActionEmbeddedResource 3
The file is included in the main generated assembly or in a satellite assembly as a resource.
prjBuildActionNone 0
No action is taken.
param($installPath, $toolsPath, $package, $project)
$configItem = $project.ProjectItems.Item("NLog.config")
# set 'Copy To Output Directory' to 'Copy if newer'
$copyToOutput = $configItem.Properties.Item("CopyToOutputDirectory")
# Copy Always Always copyToOutput.Value = 1
# Copy if Newer copyToOutput.Value = 2
$copyToOutput.Value = 2
# set 'Build Action' to 'Content'
$buildAction = $configItem.Properties.Item("BuildAction")
$buildAction.Value = 2
I have made this which copies files from my build folder to the output folder (bin/debug or bin/release). Works like a charm for me.
Nuspec file:
<package>
<files>
<file src="\bin\Release\*.dll" target="lib" />
<file src="\bin\Release\x64\*.dll" target="build\x64" />
<file src="\bin\Release\x86\*.dll" target="build\x86" />
<file src="MyProject.targets" target="build\" />
</files>
</package>
MyProject.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
<None Include="#(NativeLibs)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
You can use PowerShell and the Install.ps1 hook provided by NuGet.
See the documentation.
Via PowerShell you have to 'search' for the content element which includes your importantfile.xml in an attribute. When the script found it, it has to add <CopyToOutputDirectory>Always</CopyToOutputDirectory> as a child element.
<Content Include="importantfile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
You can find some PowerShell snippets here. Just take a look at the .ps1 files.
You could try the following (not tested). The file has to be named Install.ps1 and copied into the tools folder:
param($installPath, $toolsPath, $package, $project)
# Load project XML.
$doc = New-Object System.Xml.XmlDocument
$doc.Load($project.FullName)
$namespace = 'http://schemas.microsoft.com/developer/msbuild/2003'
# Find the node containing the file. The tag "Content" may be replace by "None" depending of the case, check your .csproj file.
$xmlNode = Select-Xml "//msb:Project/msb:ItemGroup/msb:Content[#Include='importantfile.xml']" $doc -Namespace #{msb = $namespace}
#check if the node exists.
if($xmlNode -ne $null)
{
$nodeName = "CopyToOutputDirectory"
#Check if the property already exists, just in case.
$property = $xmlNode.Node.SelectSingleNode($nodeName)
if($property -eq $null)
{
$property = $doc.CreateElement($nodeName, $namespace)
$property.AppendChild($doc.CreateTextNode("Always"))
$xmlNode.Node.AppendChild($property)
# Save changes.
$doc.Save($project.FullName)
}
}
You should also check if everything is removed completely when uninstalling the package.
Note by Jonhhy5
When updating the package via update-package, Visual Studio warns that the project is modified "outside the environnment". That's caused by $doc.Save($project.FullName). If I click reload before the command is fully terminated, it sometimes causes errors. The trick is to leave the dialog there until the process finishes, and then reload the projects.
I have written a little tool called NuGetLib to automatically add files to the nuget package after build.
create a tools folder with your Install.ps1 script
build your nugetPackage
add the tools folder to the built nugetPackage
https://stackoverflow.com/a/47134733/6229375

Octopack: Additional files copied in a pre-build event

I am trying to get Octopack to include some additional dll's that are not part of my Visual Studio project.
Is there a way to get Octopack to use my csproj files (as normal) and then specify just the additional files I need, rather than using the section in the nuspec. (Because then I have to maintain a list of all project files in my nuspec)
I am currently copying some shared libraries to a folder "/providers" in a pre-build event. Is there a way to extend on this to copy them somewhere that Octopack will see and package them?
You don't need to maintain a list of all your project files in your nuspec file, you can do this:
<files>
<file src="MyProject.Website\**\*.*" target="" />
</files>
This will grab everything in your project (all files and sub directories). If that's too much for you, you can take advantage of exclusion rules:
<files>
<file src="MyProject.Website\**\*.*" target="" exclude="MyProject.Website\docs\admin.txt" />
</files>
Hope that helps.
The downside of the answer of grabbing everything inside your project directory via the nuspec is that this could include files you don't want (see the answer's subsequent exclusion example).
A probably better way to do this is to ensure in your project that you have your files defined as "Copy to bin" or "Copy as content" and then build via msbuild w/Octopack using an additional command line argument of /p:OctoPackEnforceAddingFiles=true which tells OctoPack to combine the csproj and the nuspec, so you don't have to call out ALL the possible paths with a wildcard, you can only include files in your nuspec that aren't picked up from the regular build process. This is supported as of version 2.1.3 of OctoPack.
You could also use regular Nuget.exe and MSBuild by just specifying your csproj file, and if a nuspec exists with specific paths it should join them together.
See these reference links for better examples.
https://getadigital.com/no/blogg/setting-up-your-project-and-teamcity-octopack-for-front-end-builds/
https://octopus.com/docs/packaging-applications/nuget-packages/using-octopack/octopack-to-include-buildevent-files

Resources