NuGet to copy files into the project directory - visual-studio

I've created a .nuspec file which packages a bunch of .proto files for sharing between projects. This is great. Unfortunately, for the .proto files to be built, they need to be actually copied over to the project directory, not just referenced. Note that this is a .NET Core project.
Right now my definition creates a package that, when used, references the files in the project, but these files still reside in the original .Nuget folder, and that's not really what I need.
Here's the .nuspec definition that I've got right now:
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>GRPCProtoFiles</id>
<version>1.0.0</version>
<authors>Author</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>.proto files NuGet package.</description>
<dependencies>
<dependency id="Google.Protobuf" version="3.14.0"/>
<dependency id="Grpc" version="2.35.0"/>
</dependencies>
<contentFiles>
<files include="any/any/Protos/**/*.proto" buildAction="None" copyToOutput="false" />
</contentFiles>
</metadata>
<files>
<file src="**\*.proto" target="contentFiles\any\any\Protos" exclude="google\**" />
</files>
</package>
I know that "build action" is currently set to "none", but I can get that fixed once the files land in the correct folder upon package installation. As is, if I try to set the build action for the proto files to the correct one (which is Protobuf Compiler) then I get an error, because the files aren't where they're supposed to be.
What am I doing wrong?
EDIT:
OK, I've read up a bit on what might be happening and why. Specifically this answer gave me a lot of insight. However I'm left wondering how I can actually do what I need to do. Right now it seems that what I want to do is impossible with NuGet and I'm using the wrong tool for the job. But besides this one limitation NuGet seems ideal - it's able to pick just the .proto files from the source project and just publish that rather then pre-built libraries for various target systems.
I could, possibly, add a pre-build action to copy the files to the project dir, but I've no idea how to reference the source NuGet package folder (especially if the version changes). Any idea if this problem even has a solution?

I really know your ideas. First, make these proto files be packed into nupkg with Build Action None. And then use pre-build event under the main project to handle the files from the nupkg. The question is that how to use msbuild property to get files from nupkg since Net Core projects use Link properties to references the files under the main project rather than copy the real nuget files into the main project folder.
And under the Link properties, you could not change the Build Action of that file because it actually does not exist under the main project folder and the main project has no duty to handle the file.
There are two functions to solve it:
=============================================
Function one
You could did this directly under nupkg itself and after that, the proto files will be copied automatically during build process with the nuget package. Try the following steps and you should not add any copy task from another main project which install the nuget package.
You have to use <packages_id>.props/targets file into nupkg to get what you want. Refer to this official document.
1) create a file called <packages_id>.props file. In your side, it should be named as GRPCProtoFiles.props so that it will work.
2) add these on the GRPCProtoFiles.props file:
<Project>
<Target Name="CopyFiles" BeforeTargets="Build">
<ItemGroup>
<File Include="$(MSBuildThisFileDirectory)..\contentFiles\**\*.*"></File>
</ItemGroup>
<Copy SourceFiles="#(File)" DestinationFolder="$(ProjectDir)\Protos"></Copy>
</Target>
</Project>
3) add these on the GRPCProtoFiles.nuspec file to include the props file into nupkg:
<file src="build\*.props" target="build" />
The whole GRPCProtoFiles.nuspec file should be like this:
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>GRPCProtoFiles</id>
<version>1.0.0</version>
<authors>Author</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>.proto files NuGet package.</description>
<dependencies>
<dependency id="Google.Protobuf" version="3.14.0"/>
<dependency id="Grpc" version="2.35.0"/>
</dependencies>
<contentFiles>
<files include="any/any/Protos/**/*.proto" buildAction="None" copyToOutput="false" />
</contentFiles>
</metadata>
<files>
<file src="xxx.proto" target="contentFiles\any\any\Protos" exclude="google\**" />
<file src="build\*.props" target="build" />
</files>
</package>
4) then re-pack the new version of nuget project. Before you install the new release version, please clean nuget caches first or just delete all files under C:\Users\xxx\.nuget\packages
After the installation of the nuget package, please click Build button to execute the target.
More similar to this issue.
====================================================
Function Two
You could not do any internal steps for nupkg itself. You could directly copy the file from the external main project.
1) do not do any steps under Function One for your nuget package. Use your old nuget package.
Modify the Project A's csproj file which has installed GRPCProtoFiles nuget package.
Add <GeneratePathProperty>true</GeneratePathProperty> for PackageReference GRPCProtoFiles to generate an exclusive property as $(PkgGRPCProtoFiles) to get the path of the nupkg's content.
The whole is like this:
<ItemGroup>
<PackageReference Include="GRPCProtoFiles" Version="1.0.0">
<GeneratePathProperty>true</GeneratePathProperty>
</PackageReference>
</ItemGroup>
Then, right-click on the Project A-->Properties-->Build Events-->add these under Pre-build event Command Line:
xcopy /s /e /y /i $(PkgGRPCProtoFiles)\contentFiles\any\any\Protos $(ProjectDir)\Protos
Or, add this target into ProjectA.csproj file:
<Target Name="CopyFiles" BeforeTargets="PrepareForBuild">
<ItemGroup>
<File Include="$(PkgGRPCProtoFiles)\contentFiles\**\*.*"></File>
</ItemGroup>
<Copy SourceFiles="#(File)" DestinationFolder="$(ProjectDir)\Protos"></Copy>
</Target>

Related

Include File in NuGet package on Build, only if it does not exist

A while ago I asked this question:
Including a Folder in NuGet Package and have it install into project as file .netcore/Razor
The answer to which has got me most of the way where I want to go, but the trouble is rebuilding or updating the NuGet package in my projects is now overwriting files that are user-editable.
I feel the answer is somewhere in the .props file but I am not sure how to achieve it:
<Project>
<Target Name="CopyFiles" BeforeTargets="Build" >
<ItemGroup>
<File Include="$(MSBuildThisFileDirectory)..\Pagesettings\*.*"></File>
</ItemGroup>
<Copy SourceFiles="#(File)" DestinationFolder="$(ProjectDir)Pagesettings"></Copy>
</Target>
</Project>
The Target tag has an optional attribute Condition. I am guessing that somehow this can be used...?
So, to clarify my question:
From my original question linked above I have a NuGet package which when downloaded creates a folder called PageSettings, into which is copied a bunch of default JSON files containing PageSettings from my NuGet package. NOTE; these files initially appear in Visual Studio as linked files. They are not actually copied to the directory until the project is built.
However, the purpose of these PageSettings files is to be a default , or if you like an example, of PageSettings for users of my NuGet package. Once installed, the user can and should change these .JSON files, so now when their project is rebuilt, or if there is an update to the NuGet package available which they install, these JSON files should NOT update/install.
Sure. My answer quite has some flaws. To prevent overwriting existing files and files that have changed, I suggest you use xcopy with /d. It will not overwrite the existing files.
Instead, use this in test.props file:
<Project>
<Target Name="CopyFiles" BeforeTargets="Build">
<ItemGroup>
<File Include="$(MSBuildThisFileDirectory)..\Pagesettings\*.*"></File>
</ItemGroup>
<Exec Command="xcopy /s/d/y #(File) $(ProjectDir)Pagesettings\"></Exec>
<!--<Copy SourceFiles="#(File)" DestinationFolder="$(PorojectDir)Pagesettings" Condition="!Exists('$(PorojectDir)Pagesettings\*.*')"></Copy>-->
</Target>
</Project>
Then, repack your nuget project. Before using this new one, please clean nuget caches first.

Cannot copy folder with nuget package in .net core

I Create a NuGet package and install into another project. but don't copy a file into the location project. only my file reference to NuGet package and I change the code, The package also changes! I want to copy the package to the target project.
<?xml version="1.0"?>
<package>
<metadata minClientVersion="3.3.0">
<id>MyPackage</id>
<version>1.0.0</version>
<authors>Meysam</authors>
<owners>Meysam</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Package description</description>
<releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
<copyright>Copyright 2018</copyright>
<tags>Tag1 Tag2</tags>
<contentFiles>
<files include="cs/*.*/**" buildAction="Compile" />
</contentFiles>
</metadata>
<files>
<file src="ConsoleApp1\CustomClass\CustomClass.cs" target="contentFiles\cs\any\CustomClass" />
</files>
</package>
Copying files into the project that uses a nuget package is only supported when using packages.config, which isn't supported by SDK-style projects, which are required for .NET Core projects. As your screenshots show, contentFiles in PackageReferences projects are included at build time. The little arrow icon in the bottom right corner of the C# and folder icons you pointed to in your screenshot are visual indicators that the file and folder are different to the other files and folders in your project. In fact, that little icon overlay is similar to what Windows adds for shortcuts, so if you understand a shortcut is a file that "points to" another file, it should make sense that these are shortcuts to files outside your project, but are included as part of your project.
Anyway, it's working as designed.
You'll need to find another way to do whatever you want, but you didn't describe why you're trying to include a file in the project that references your package, so I can't give direct advice. My best advice, if you are experienced with ASP.NET and ASP.NET Core is think about how ASP.NET used to read a lot of settings directly from web.config, but ASP.NET Core instead uses a builder pattern, so that users are not forced to store settings in web.config and can store settings anywhere they want. If what you're doing is similar, your package users will have a better experience if you provide them with a configuration builder that they can override, rather than having a file in their project that they must edit and gets overwritten every time they upgrade to a new version of your package.
Cannot copy folder with nuget package in .net core
Yes, just like zivkan said:
Copying files into the project that uses a nuget package is only
supported when using packages.config
But, we could use a workaround to resolve this issue. We could add a copy task in the xx.targets file, and set this file in the \build folder in the .nuspec file.
The content of mypackage.targets file:
<Target Name="CopyFile" AfterTargets="AfterBuild">
<ItemGroup>
<CopyFiles Include="$(NuGetPackageRoot)\mypackage\4.0.0\cs\*.*\**" />
</ItemGroup>
<Copy
SourceFiles="#(CopyFiles)"
DestinationFolder="$(ProjectDir)"
/>
</Target>
And the .nuspec file:
<file src="xxx\xxx\mypackage.targets" target="build" />
Hope this helps.

Automatically add project dependency metadata to manually created nuspec file

When I nuget pack a web project I want to specify custom unpack locations for content and maintain project dependency metadata.
Given the following manually created example nuspec file:
<?xml version="1.0"?>
<package>
<metadata>
<id>Web.MyApp</id>
<version>1.0</version>
<title>Web.MyApp</title>
<authors>Chris</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Nuget package containing files for Web.MyApp</description>
<releaseNotes>release notes</releaseNotes>
<copyright>Copyright Chris 2017</copyright>
<tags />
<dependencies />
</metadata>
<files>
<file src="bin\**\*.*" target="bin" />
<file src="views\**\*.*" target="views" />
<file src="content\" target="content" />
<file src="scripts\" target="scripts" />
<file src="Global.asax" target="" />
<file src="*.config" target="" />
</files>
</package>
This allows me to specify custom unpack locations for bin, content, sprint folders etc but I want the project dependency metadata to be automatically maintained. I don't want to edit the nupsec each time a new dependency is referenced.
As an attempt to resolve this problem I tried to nuget pack the csproj file instead of the nuspec. This maintained the dependency metadata however it made specifying content unpack locations much trickier. I can do the following:
<Content Include="Content\dist\images\brand-logo.svg">
<Pack>true</Pack>
<PackagePath>Content\Content\dist</PackagePath>
</Content>
But I couldn't find an elegant solution for the bin folder. I just feel like I'm doing something fundamentally wrong.
So my question is, how can I automatically maintain project dependency metadata when creating a nuget package from a manually created nuspec file?
The pack command I am using:
..\tools\nuget\nuget.exe pack $project + ".nuspec" -IncludeReferencedProjects -
Properties Configuration=Release -Verbosity quiet -NonInteractive -
OutputDirectory "$packagedirectory" -Version $buildVersion -Symbols
Automatically add project dependency metadata to manually created nuspec file
If I understand you correct, I am afraid you have already automatically add project dependency metadata to manually created .nuspec file. You just need to rebuild the project and re-pack the .nuspec file.
When you include the referenced files with wildcard, it will contain the new added project references:
<file src="bin\**\*.*" target="bin" />
Add a new project reference to the project, then re-build the project, the dll file of referenced project will be copied to the \bin folder, So we just need to re-pack the .nuspec file, the referenced project metadata will included in the new created package.
For example, add a Atest reference project to Web.MyApp project, then rebuild the project and re-pack the .nuspec file:
If I misunderstand you, please let me know for free.

nuspec contentFiles not added to a project

I have a web project (mvc5) with a project.json inside.
Also, I have a nuget package. Inside this package (besides the dll reference) I have some Content files (cshtml files, css, javascript etc).
There are 2 goals to achieve:
After installing the package to the project I want to get Content Files included to the project.
After Building the project I want nuget to restore the content files
The nuspec file:
<?xml version="1.0"?>
<package>
<metadata>
/.../
<dependencies>
<group targetFramework="net461">
/.../
</group>
</dependencies>
<contentFiles>
<files include="**/*.*" flatten="false" copyToOutput="false" buildAction="Content" />
</contentFiles>
</metadata>
<files>
/.../
<file src="..\src\Content\**\*.*" target="contentFiles\any\any\Content" />
<file src="..\src\Views\**\*.*" target="contentFiles\any\any\Views" />
</files>
</package>
Well, Visual Studio is adding nuget package with no issues. The reference is also included. But the content is always missing. The content files are not copied from the /packages/ folder and are not included to the project.
The same is when I build the project - it just copies the dll and does not touch content files from the package at all.
It only works if I have copyToOutput="true", but all content goes to bin folder, of course.
The reason why I need to restore the content files is that the files from the nuget package are ignored by a tfs 'ignore file' (.tfignore). I don't want to have these content files to be a part of the web project in TFS. However, of course, TFS build server fails to build the project, because some content files are missing (the files from the package). So I want nuget on the build server to restore the content files before it stats building the project.
Any idea how to get this work if it is possible? Or maybe this is not possible and I must have content files to be included to the project?
you could add a msbuild target to your package. in this target you can execute the nuget restore command...
example:
<?xml version="1.0"?>
<package >
<metadata>
<!-- ... -->
</metadata>
<files>
<!-- Include your MSBuild target to \build -->
<file src="build\myNuGetRestoreTarget.targets" target="build" />
</files>
</package>
see:
https://learn.microsoft.com/en-us/nuget/create-packages/creating-a-package#including-msbuild-props-and-targets-in-a-package
This is by design. NuGet packages are no longer supposed to modify the project's source but only add to its built output or build process (in addition to .NET libraries). The content folder for adding sources to the project is only continued to be supported for packages.config based projects since it would be a breaking change for existing projects, but projects using project.json or PackageReference (VS 2017) get the new behaviour.
I was in the situation. Here's how I handled it.
Don't keep your files as content, content files are added to project files. Rename the target to something else. I kept it as drop. Your nuspec file should be like:
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>ID</id>
<version>1.0</version>
<title>Title</title>
<authors>J</authors>
<owners>J</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Binary Files such as images, fonts, icons and svgs</description>
<copyright>J</copyright>
</metadata>
<files>
<file src="..\..\Internal.Resources.targets" target="build" />
<file src="..\..\**\*.*" target="drop" exclude="**\*.targets"/>
</files>
</package>
Keep a target file as one of your drop file. Like following:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<RequiredFiles Include="$(MSBuildThisFileDirectory)..\drop\**\*.*" />
<None Include="#(RequiredFiles)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Now your nuget install will just extract the content in packages folder, add target to your project. When you build, the targets will fetch files from package directory and put in build directory.

How to deal with relative NuGet packages paths to facilitate easy pckages dir relocation with NuGet.Config?

We moved NuGet's packages folder to some other place and created a NuGet.Config file at the slns level:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="repositoryPath" value="../.nugetPackages" />
</config>
</configuration>
We also have this in few of our csproj files:
<Import Project="..\..\..\Proj\packages\Microsoft.Azure.DocumentDB.1.7.1\build\Microsoft.Azure.DocumentDB.targets" Condition="Exists('..\..\..\Proj\packages\Microsoft.Azure.DocumentDB.1.7.1\build\Microsoft.Azure.DocumentDB.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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 {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\Proj\packages\Microsoft.Azure.DocumentDB.1.7.1\build\Microsoft.Azure.DocumentDB.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\Proj\packages\Microsoft.Azure.DocumentDB.1.7.1\build\Microsoft.Azure.DocumentDB.targets'))" />
</Target>
And this too:
<Import Project="..\..\packages\PostSharp.4.2.17\tools\PostSharp.targets" Condition="Exists('..\..\packages\PostSharp.4.2.17\tools\PostSharp.targets')" />
<Target Name="EnsurePostSharpImported" BeforeTargets="BeforeBuild" Condition="'$(PostSharp30Imported)' == ''">
<Error Condition="!Exists('..\..\packages\PostSharp.4.2.17\tools\PostSharp.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://www.postsharp.net/links/nuget-restore." />
<Error Condition="Exists('..\..\packages\PostSharp.4.2.17\tools\PostSharp.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://www.postsharp.net/links/nuget-restore." />
</Target>
My question is how to deal with such relative package paths in the csproj files to keep things generic (so we could easily move the package folder anytime anywhere)?
Is there maybe some variable we can use like $NuGetPackagesDir or something like that?
(We use Vs2015, NuGet 5.3 and TeamCity build server [I don't remember current version..])
You cannot really have a generic path and still use the NuGet tooling within Visual Studio since when you update a package the hint path will be updated by NuGet back to a relative path if you are using a packages.config file.
Whilst you could specify a global packages folder the hint paths will be relative to that path depending on where you put the solution.
The hint path problem goes away when you use a project.json file or have a new .NET Core style project that uses PackageReference elements in the .csproj file. If you are using a packages.config file then your .csproj file will have hint paths pointing to assemblies within the packages folder.

Resources