Home: How to ensure content path of a nuget package in VS 2017?

Created on 21 Dec 2017  路  39Comments  路  Source: NuGet/Home

Hi,

I have the following scenario _"NuGet-Package A"_ have a content file, e.g. a .config.

<ItemGroup Label="Content"> <Content Include="App_Config\Include\Indeca.SitecoreCommon\Indeca.SitecoreCommon.EditorExtensions.DeviceDependent.config" /> </ItemGroup>

The package content looks okay:

image

image

_"NuGet-Package B"_ consumes _"NuGet Package A"_, the project structure looks okay, but if I build the package, the package content does not have the correct content path:

image

image

How can I ensure that the content path of the config in "NuGet-Package B" have the right structure?

Regards
Dirk

NuGet product used: VS 2017 UI

dotnet.exe --version (if appropriate): VS 2017 15.5.2

VS version (if appropriate): VS 2017 15.5.2

OS version (i.e. win10 v1607 (14393.321)): Win10

Worked before? If so, with which NuGet version:

Detailed repro steps so we can see the same problem

  1. Create a simple project A with a content file with a folder structure

  2. Create package and consume this package in another project B

  3. Create a package of project B, folder structure will be lost in the package content

ContentFiles Icebox Question

All 39 comments

@monkey-dsc can you please give us the nuspec file for packageB (Indeca.SitecoreCommon.PresentationSettings)?

Hi mishra,

sorry for the late response, I was on vacation over the years.

I have attached the .nuspec file for the Indeca.SitecoreCommon.PresentationSettings project. For example the Indeca.SitecoreCommon.EditorExtensions.DeviceDependent.config should be in a folder structure like App_Config/Include/Indeca.SitecoreCommon/Indeca.SitecoreCommon.EditorExtensions.DeviceDependent.config, like shown in the screenshot above.

Best regards
Dirk

Indeca.SitecoreCommon.PresentationSettings.nuspec.zip

@monkey-dsc sorry for lack of detail, if you are packing using visual studio or msbuild /t:pack or dotnet.exe pack to pack your projects, can you please zip up the obj folder of both projects and send it to us? it should contain .g.props , .g.targets, nuspecs, and project.assets.json files. you can remove any DLLs from it.

Hi @rohit21agrawal ,

I have attached both obj-folders with all files you requested.

Best regards
Dirk

Indeca.SitecoreCommon.EditorExtensions.DeviceDependent_obj.zip
Indeca.SitecoreCommon.PresentationSettings_obj.zip

thanks @monkey-dsc , I am currently investigating this.
This might be a bigger bug than i thought, because my expectation is that contentFiles from a package (say PacakgeB) should not appear in package made out of ProjectA if ProjectA references PackageB. I will investigate this more and get back to you.

Is it also possible to paste both your csproj files for an even closer inspection? I didn't think I would need it, but I am seeing a behavior I can't make sense of right now.

Hi @rohit21agrawal ,

I will attach both projects completely, there is no magic inside ;)

I also expect that content files should not appear in the other project as content files, but they does...

Best regards
Dirk

Indeca.SitecoreCommon.EditorExtensions.DeviceDependent.zip
Indeca.SitecoreCommon.PresentationSettings.zip

Hi @rohit21agrawal ,

are there any updates on this?

Best regards
Dirk

Hi @rohit21agrawal

I also investigate this problem any see:

Current version (the version used in VS 15.7.1) generate content link as:

    <Content Include="$(NuGetPackageRoot)moduleb\1.0.0\contentFiles\any\netstandard2.0\Features\Contents\css\station.css" Condition="Exists('$(NuGetPackageRoot)moduleb\1.0.0\contentFiles\any\netstandard2.0\Features\Contents\css\station.css')">
      <NuGetPackageId>ModuleB</NuGetPackageId>
      <NuGetPackageVersion>1.0.0</NuGetPackageVersion>
      <NuGetItemType>Content</NuGetItemType>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <TargetPath>Features\Contents\css\station.css</TargetPath>
      <DestinationSubDirectory>Features\Contents\css\</DestinationSubDirectory>
      <Private>True</Private>
      <Link>Features\Contents\css\station.css</Link>
    </Content>

For restore phase, the generated item have problem:

  • Missing: <Pack>false</Pack> item metadata
  • Not have a Label: should we having a label for all generated content link to let people easy update metadata of nuget linked content easier when needed.
  • Not generated any linked content item for parent module. (ProjectC reference ModuleB reference ModuleA: not generated content item for content from ModuleA inside ProjectC)

For pack phase, most of thing may come from method PackTaskLogic.GetContentMetadata:

  • Not any block code reading item metadata: TargetPath, DestinationSubDirectory, Link
  • This problem not only affected to generated content link from nuget, it's also affected to all manual linked item.

@monkey-dsc If you need a solution right now, add this hack to PackageB project file will help you have correct path, or set <Pack>false</Pack> to prevent content of PackageA become content of PackageB

    <Content Update="$(NuGetPackageRoot)**">
      <Pack>true</Pack>
      <PackagePath>content;contentFiles/any/$(TargetFramework)</PackagePath>
      <NuGetRecursiveDir>%(DestinationSubDirectory)</NuGetRecursiveDir>
    </Content>

Hi @anhphan,

that didn't work for me :( The project is in new csproj-format and I got the following error:

The <Content> element below the <Project> element is unknown.

Any tips?

Best regards
Dirk

Hi @monkey-dsc

You need nested <Content> element inside <ItemGroup> element:

  <ItemGroup>
   <Content Update="$(NuGetPackageRoot)**">
      <Pack>true</Pack>
      <PackagePath>content;contentFiles/any/$(TargetFramework)</PackagePath>
      <NuGetRecursiveDir>%(DestinationSubDirectory)</NuGetRecursiveDir>
    </Content>
  </ItemGroup>

Hi @anhphan,

okay I did it like you described, but still same behaviour:
image

:(

Hi @monkey-dsc,

Can you try to update VS to version >= 15.7, when i investigate that issue, my VS version is 15.7
and Nuget pack task will using NuGetRecursiveDir value when we set PackagePath for content files.

Hi @anhphan,

I'm on VS 15.7.3

Can you try clean and then rebuild,

And can you should me the content of file: [ProjectName].csproj.nuget.g.props inside obj folder

Already tried to clean... here are the files... I also attached the .csproj file

obj.zip

Oh, see the problem

Not sure why, but your generated nuget.props file dint have DestinationSubDirectory metadata on Content item, which i use to set directory for packed file:

    <Content Include="$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta015\contentFiles\any\net461\sitecore\shell\Applications\Content Manager\Dialogs\LayoutDetails\LayoutDetails.xml" Condition="Exists('$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta015\contentFiles\any\net461\sitecore\shell\Applications\Content Manager\Dialogs\LayoutDetails\LayoutDetails.xml')">
      <NuGetPackageId>Indeca.SitecoreCommon.EditorExtensions.DeviceDependent</NuGetPackageId>
      <NuGetPackageVersion>823.0.0-beta015</NuGetPackageVersion>
      <NuGetItemType>Content</NuGetItemType>
      <Private>False</Private>
      <Link>sitecore\shell\Applications\Content Manager\Dialogs\LayoutDetails\LayoutDetails.xml</Link>
    </Content>

Please change the hack to

  <ItemGroup>
   <Content Update="$(NuGetPackageRoot)**">
      <Pack>true</Pack>
      <PackagePath>content;contentFiles/any/$(TargetFramework)</PackagePath>
      <NuGetRecursiveDir>%(Link)</NuGetRecursiveDir>
    </Content>
  </ItemGroup>

Or maybe you just simple delete obj folder to let VS generated it again

I give up... all I try doesn't solve it :(, but anyway many thanks for your help!

Hi @anhphan,

a last try, here are both projects can you have a look at them? Maybe I miss something else ...

Thanks in advance.

Packages.zip

Hi @monkey-dsc,

Nothing wrong with project file. I had review the code of NuGet.Build.Tasks.Pack.dll in visual studio folder (_C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\Sdks\NuGet.Build.Tasks.Pack_) and see having difference between netcore version and desktop version.

The netcore version have fallback to NuGetRecursiveDir if RecursiveDir metadata is empty, but desktop version doesn't.

So that hack maybe only apply to netcore project (while your target is net461), because RecursiveDir is readonly metadata and we cannot change it.

Damn, so then I keep on walking in a circle...

Many, many thanks for your help!

@rohit21agrawal do you have any updates?

@anhphan @monkey-dsc the copy of NuGet.Build.Tasks.Pack.dll at C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\Sdks\NuGet.Build.Tasks.Pack is not used anymore. We only use the copy in C:\Program Files\dotnet.....

@monkey-dsc i just investigated your project files that you sent me.
I trimmed them down to the bare minimum, and I see the solution explorer showing your file in the right hierarchy:

image

Packages.zip

I have attached the two projects that i use. The nupkg created from Indeca.SitecoreCommon.EditorExtensions.DeviceDependent is added as a PackageReference to the Indeca.SitecoreCommon.PresentationSettings project.

Thank @rohit21agrawal for the info,

I check on my machine, dotnet sdk version <= 2.1.104 don't using NuGetRecursiveDir fallback for RecursiveDir metadata
```C#
private IEnumerable GetContentMetadata(IMSBuildItem packageFile, string sourcePath, PackArgs packArgs, string[] contentTargetFolders)
{
List list = Enumerable.ToList(Enumerable.Select(contentTargetFolders, new Func(PathUtility.EnsureTrailingSlash)));
bool flag = Enumerable.Contains(packageFile.Properties, "PackagePath");
if (flag)
{
string property = packageFile.GetProperty("PackagePath");
list = (property == null ? Enumerable.ToList(new string[] { string.Empty }) : Enumerable.ToList(Enumerable.Distinct(MSBuildStringUtility.Split(property))));
string str = packageFile.GetProperty("RecursiveDir");
if (!string.IsNullOrEmpty(str))
{

Only dotnet sdk version >= 2.1.200 using that metadata:
```C#
        private IEnumerable<ContentMetadata> GetContentMetadata(IMSBuildItem packageFile, string sourcePath, PackArgs packArgs, string[] contentTargetFolders)
        {
            List<string> list = contentTargetFolders.Select<string, string>(new Func<string, string>(PathUtility.EnsureTrailingSlash)).ToList<string>();
            bool flag = packageFile.Properties.Contains<string>("PackagePath");
            if (flag)
            {
                string property = packageFile.GetProperty("PackagePath");
                list = (property == null ? (new string[] { string.Empty }).ToList<string>() : MSBuildStringUtility.Split(property).Distinct<string>().ToList<string>());
                string str = packageFile.GetProperty("RecursiveDir");
                str = (string.IsNullOrEmpty(str) ? packageFile.GetProperty("NuGetRecursiveDir") : str);
                if (!string.IsNullOrEmpty(str))
                {

@monkey-dsc
Can you check what sdks you have (check folder under C:\Program Files\dotnet\sdk)
and output of:

dotnet --info
dotnet --version

@rohit21agrawal The problem not on visual studio display, problem at generate nuspec, VS using Link metadata for display inside, but pack logic not using any metadata: TargetPath, DestinationSubDirectory, Link as current code: PackTaskLogic.GetContentMetadata

My hack is just set PackagePath to force it using NugetRecursiveDir metadata and change value of this metadata to expected value

Thanks @anhphan for pointing it out.
@monkey-dsc this is the exact hack you would need in your csproj:

<PropertyGroup>

<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);MyCustomTarget</TargetsForTfmSpecificContentInPackage>

</PropertyGroup>

<ItemGroup>
        <Content Update="$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta016\contentFiles\any\net461\App_Config\Include\Indeca.SitecoreCommon\Indeca.SitecoreCommon.EditorExtensions.DeviceDependent.config">
            <PackagePath>content</PackagePath>
            <NuGetRecursiveDir>%(Link)</NuGetRecursiveDir>
        </Content>
    </ItemGroup> 

    <Target Name="MyCustomTarget">
    <ItemGroup>
        <TfmSpecificPackageFile Include="@(Content)">
            <PackagePath>contentFiles/any/$(TargetFramework)/%(Link)</PackagePath>
        </TfmSpecificPackageFile>
        </ItemGroup>
    </Target>

The documentation for the extension points I used in the above example are available here: https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#advanced-extension-points-to-create-customized-package

I think @monkey-dsc doesn't have latest dotnet sdk (>= 2.1.200).
Actualy, I am using this in all my Directory.Build.targets for fixing not only nuget package reference, it's also fix path for linked content:

  <!-- Ensure target BeforeBuild exists -->
  <Target Name="BeforeBuild" />

  <!-- Exclude package reference content asset from pack except it explicit include -->
  <Target Name="ExcludePackageReferenceContentAssetFromPack" BeforeTargets="BeforeBuild">
    <ItemGroup>
      <Content Update="*" Condition="%(Content.NuGetPackageId) != '' and %(Content.Pack) != 'true'">
        <Pack>false</Pack>
      </Content>
    </ItemGroup>
  </Target>

  <!-- Nuget packing wrong path for linked content -->
  <!-- This fix apply for single target framework only -->
  <Target Name="FixPackTargetPathForLinkedContent" BeforeTargets="BeforeBuild" Condition="$(TargetFramework) != ''" >
    <ItemGroup>
      <Content Update="*" Condition="%(Content.Link) != ''">
        <PackagePath>content;contentFiles/any/$(TargetFramework)</PackagePath>
        <NuGetRecursiveDir>%(Content.Link)</NuGetRecursiveDir>
      </Content>
    </ItemGroup>
  </Target>

Hi @rohit21agrawal,

thanks a lot this gets very close to a solution! I have added the following to my project file:
<PropertyGroup> <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);FixPackTargetPathForLinkedContent</TargetsForTfmSpecificContentInPackage> </PropertyGroup>
<!-- Nuget packing wrong path for linked content --> <Target Name="FixPackTargetPathForLinkedContent" BeforeTargets="BeforeBuild" Condition="$(TargetFramework) != ''"> <ItemGroup> <Content Update="$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta016\content\App_Config\Include\Indeca.SitecoreCommon\Indeca.SitecoreCommon.EditorExtensions.DeviceDependent.config"> <PackagePath>content</PackagePath> <NuGetRecursiveDir>%(Link)</NuGetRecursiveDir> </Content> <Content Update="$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta016\contentFiles\any\net461\App_Config\Include\Indeca.SitecoreCommon\Indeca.SitecoreCommon.EditorExtensions.DeviceDependent.config"> <PackagePath>content</PackagePath> <NuGetRecursiveDir>%(Link)</NuGetRecursiveDir> </Content> <Content Update="$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta016\content\sitecore\shell\Applications\Content Manager\Dialogs\LayoutDetails\LayoutDetails.xml"> <PackagePath>content</PackagePath> <NuGetRecursiveDir>%(Link)</NuGetRecursiveDir> </Content> <Content Update="$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta016\contentFiles\any\net461\sitecore\shell\Applications\Content Manager\Dialogs\LayoutDetails\LayoutDetails.xml"> <PackagePath>content</PackagePath> <NuGetRecursiveDir>%(Link)</NuGetRecursiveDir> </Content> </ItemGroup> <ItemGroup> <TfmSpecificPackageFile Include="@(Content)"> <PackagePath>content/%(Link)</PackagePath> </TfmSpecificPackageFile> </ItemGroup> <ItemGroup> <TfmSpecificPackageFile Include="@(Content)"> <PackagePath>contentFiles/any/$(TargetFramework)/%(Link)</PackagePath> </TfmSpecificPackageFile> </ItemGroup> </Target>

This leads to the following result:

image

Really close to what it should be, except the yellow marked files, I don't know where they come from or picked up correctly, can you help we out with that?

Another question is: How can I get rid of those absolute paths to the nuget content?
<Content Update="$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta016\contentFiles\any\net461\sitecore\shell\Applications\Content Manager\Dialogs\LayoutDetails\LayoutDetails.xml">

Are there any wildcard paths possible? If the nuget package will be updated I always have to update the project file manually...

Best regards
Dirk

Hi @monkey-dsc
When using TfmSpecificPackageFile with specific path, I don't think you need to set NuGetRecursiveDir, just simple set Pack to false to exclude it.

You can try to change config to:

<Target Name="FixPackTargetPathForLinkedContent" BeforeTargets="BeforeBuild" Condition="$(TargetFramework) != ''">
    <ItemGroup>
        <Content Update="$(NuGetPackageRoot)**">
                        <Pack>false</Pack>
        </Content>
    </ItemGroup>
    <ItemGroup>
        <TfmSpecificPackageFile Include="@(Content)">
            <PackagePath>content/%(Link)</PackagePath>
        </TfmSpecificPackageFile>
    </ItemGroup>
    <ItemGroup>
        <TfmSpecificPackageFile Include="@(Content)">
            <PackagePath>contentFiles/any/$(TargetFramework)/%(Link)</PackagePath>
        </TfmSpecificPackageFile>
    </ItemGroup>
</Target>

Hi @anhphan ,

but if I set <Pack>false</Pack> the project itself can't have any content files, am I right?

At the moment it works if I set <Pack>false</Pack>, but I can't use content files in that project (which is not required right now, but maybe a future requirement.)

Best regards
Dirk

as for wildcards, you can do something like:

```
$(NuGetPackageRoot)indeca.sitecorecommon.editorextensions.devicedependent\823.0.0-beta016***

@monkey-dsc I only set pack metadata to false for content files from nuget package only, so it not effect your current project content file, as you see it only up date content inside nuget cache folder $(NugetPackageRoot), i also used wildcard for them:

<Content Update="$(NuGetPackageRoot)**">
       <Pack>false</Pack>
</Content>

Hi @rohit21agrawal,

I think we should keep this still open, currently we having 2 problem as i pointed before:

  • Generated Linked Content for Nuget Package Reference missing metadata <Pack>false</Pack>
  • Packing content have wrong target path for all Link Content (pack logic not using Link metadata anywhere)

_sample of generated content (msbuild document say Private metadata only have meaning for Reference item type)_

    <Content Include="$(NuGetPackageRoot)moduleb\1.0.0\contentFiles\any\netstandard2.0\Features\Contents\css\station.css" Condition="Exists('$(NuGetPackageRoot)moduleb\1.0.0\contentFiles\any\netstandard2.0\Features\Contents\css\station.css')">
      <NuGetPackageId>ModuleB</NuGetPackageId>
      <NuGetPackageVersion>1.0.0</NuGetPackageVersion>
      <NuGetItemType>Content</NuGetItemType>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <TargetPath>Features\Contents\css\station.css</TargetPath>
      <DestinationSubDirectory>Features\Contents\css\</DestinationSubDirectory>
      <Private>True</Private>
      <Link>Features\Contents\css\station.css</Link>
    </Content>

@anhphan not planning on closing it, just adding a label. but you are right.

Would you be interested in trying to make a fix for this? I can help as and when needed. As a pointer, you just need to augment this condition to say that there shouldn't be a NuGetPackageId associated with it: https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Build.Tasks.Pack/NuGet.Build.Tasks.Pack.targets#L459

I would make a PR for wrong path problem in next week, but don't know how to fix the missing Pack metadata problem.

have had the same metadata problem :(

Was this page helpful?
0 / 5 - 0 ratings