Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.

Commit 187ce0a

Browse files
kzuadalon
authored andcommitted
Improvements to Content management for consistency
Prevent users from using `contentFiles` as a relative dir in their @content items, since that would be a user error since the folder is already added to Content items with no CopyToOutputDirectory. Keep the original behavior where Content is by default: - CodeLanguage=any - TargetFramework=current TFM. Meaning that Content is by default %(FrameworkSpecific)=true. This can obviously be overriden by either: - Adding an explicit %(TargetFramework) to target (i.e. 'net46' or 'any') - Adding %(FrameworkSpecific)=false Turned off inclusion of None items by default, since they basically serve the same purpose as raw PackageFile. Turning it on by default could mean inadvertently including stuff that we shouldn't. Included additional tests for the metadata overrides for lang/tfm. NOTE: we're not defaulting %(Content.TargetFramework) via an ItemDefinitionGroup to avoid potentially colliding with other targets in what seems like a super generic metadata name. So instead, we process the default in AssignPackagePath and assign that default value to the resulting items, so the calculated/inferred value can be inspected if necessary. Also, added the PackageFile as an AvailableItemName so that users can trivially select it as the Build Action for an item, and cause it to be included in the package by default. This would make using None completely unnecessary.
1 parent 4fbc5fe commit 187ce0a

17 files changed

Lines changed: 203 additions & 28 deletions

src/Build/NuGet.Build.Packaging.Tasks/AssignPackagePath.cs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,38 +117,62 @@ ITaskItem EnsurePackagePath(ITaskItem file, IDictionary<string, ITaskItem> kindM
117117
// Special case for contentFiles, since they can also provide a codeLanguage metadata
118118
if (packageFolder == PackagingConstants.Folders.ContentFiles)
119119
{
120-
/// See https://docs.nuget.org/create/nuspec-reference#contentfiles-with-visual-studio-2015-update-1-and-later
120+
if (file.GetMetadata("TargetPath").StartsWith(packageFolder, StringComparison.OrdinalIgnoreCase))
121+
{
122+
Log.LogErrorCode(nameof(ErrorCode.NG0013), ErrorCode.NG0013(file.GetMetadata("TargetPath")));
123+
// We return the file anyway, since the task result will still be false.
124+
return file;
125+
}
126+
127+
// See https://docs.nuget.org/create/nuspec-reference#contentfiles-with-visual-studio-2015-update-1-and-later
121128
var codeLanguage = file.GetMetadata(MetadataName.ContentFile.CodeLanguage);
122129
if (string.IsNullOrEmpty(codeLanguage))
130+
{
123131
codeLanguage = PackagingConstants.AnyFramework;
132+
output.SetMetadata(MetadataName.ContentFile.CodeLanguage, codeLanguage);
133+
}
124134

125135
packageFolder = Path.Combine(packageFolder, codeLanguage);
126136

127137
// And they also cannot have an empty framework, at most, it will be "any"
128138
if (string.IsNullOrEmpty(targetFramework))
129139
targetFramework = PackagingConstants.AnyFramework;
140+
141+
// Once TF is defaulted, a content file is actually always framework-specific,
142+
// although the framework may be 'any'.
143+
frameworkSpecific = true;
144+
145+
// At this point we have the correct target framework for a content file, so persist it.
146+
output.SetMetadata(MetadataName.TargetFramework, targetFramework);
130147
}
131148

149+
var targetPath = file.GetMetadata("TargetPath");
132150
// NOTE: TargetPath allows a framework-specific file to still specify its relative
133151
// location without hardcoding the target framework (useful for multi-targetting and
134-
// P2P references)
135-
var targetPath = file.GetMetadata("TargetPath");
152+
// P2P references).
136153
if (string.IsNullOrEmpty(targetPath))
137154
{
138-
targetPath = kind == PackageItemKind.None ?
139-
// For None, preserve the relative dir.
140-
file.GetMetadata("RelativeDir") + file.GetMetadata("FileName") + file.GetMetadata("Extension") :
155+
targetPath = string.IsNullOrEmpty(packageFolder) ?
156+
Path.Combine(file.GetMetadata("RelativeDir"), file.GetMetadata("FileName") + file.GetMetadata("Extension")) :
157+
// Well-known folders only get root-level files by default. Can be overriden with PackagePath or TargetPath
158+
// explicitly, of course
141159
file.GetMetadata("FileName") + file.GetMetadata("Extension");
142160
}
143161

144-
// None files or those for which we know no mapping, go straight to the root folder of the package,
145-
// respecting their RelativeDir.
162+
if (!string.IsNullOrEmpty(packageFolder) &&
163+
targetPath.StartsWith(packageFolder + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
164+
{
165+
// Avoid duplicating already determined package folder in package path later on.
166+
targetPath = targetPath.Substring(packageFolder.Length + 1);
167+
}
168+
169+
// If we have no known package folder, files go to their RelativeDir location.
146170
// This allows custom packaging paths such as "workbooks", "docs" or whatever, which aren't prohibited by
147171
// the format.
148172
var packagePath = string.IsNullOrEmpty(packageFolder) ?
149173
// File goes to the determined target path (or the root of the package), such as a readme.txt
150174
targetPath :
151-
frameworkSpecific ?
175+
frameworkSpecific ?
152176
// Otherwise, it goes to a framework-specific folder.
153177
Path.Combine(new[] { packageFolder, targetFramework }.Concat(targetPath.Split(Path.DirectorySeparatorChar)).ToArray()) :
154178
// Except if frameworkSpecific is false, such as for build, tools, runtimes

src/Build/NuGet.Build.Packaging.Tasks/NuGet.Build.Packaging.Inference.targets

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Copyright (c) .NET Foundation. All rights reserved.
2525
<!-- Whether to include @(Content) items with CopyToOutputDirectory != '' in the package -->
2626
<IncludeContentInPackage Condition="'$(IncludeContentInPackage)' == ''">true</IncludeContentInPackage>
2727
<!-- Whether to include @(None) items with CopyToOutputDirectory != '' in the package -->
28-
<IncludeNoneInPackage Condition="'$(IncludeNoneInPackage)' == ''">true</IncludeNoneInPackage>
28+
<IncludeNoneInPackage Condition="'$(IncludeNoneInPackage)' == ''">false</IncludeNoneInPackage>
2929
<!-- Whether to include @(BuiltProjectOutputGroupOutput), @(DocumentationProjectOutputGroupOutput) and @(SatelliteDllsProjectOutputGroupOutput) items in the package -->
3030
<IncludeOutputsInPackage Condition="'$(IncludeOutputsInPackage)' == ''">true</IncludeOutputsInPackage>
3131
<!-- Whether to include @(DebugSymbolsProjectOutputGroupOutput) items in the package -->
@@ -88,7 +88,6 @@ Copyright (c) .NET Foundation. All rights reserved.
8888
</_InferredPackageFile>
8989

9090
<!-- NOTE: Content is opt-out (must have Pack=false to exclude if IncludeContentInPackage=true) -->
91-
<!-- @ContentFilesProjectOutputGroupOutput = @(ContentWithTargetPath -> '%(FullPath)') -->
9291
<_InferredPackageFile Include="@(ContentWithTargetPath->'%(FullPath)')"
9392
Condition="
9493
'%(ContentWithTargetPath.Pack)' == 'true' or
@@ -98,13 +97,14 @@ Copyright (c) .NET Foundation. All rights reserved.
9897
<Kind Condition="'%(ContentWithTargetPath.Kind)' == '' and '%(ContentWithTargetPath.CopyToOutputDirectory)' == ''">Content</Kind>
9998
</_InferredPackageFile>
10099

101-
<!-- NOTE: None is also opt-out (must have Pack=false to exclude if IncludeNoneInPackage=true, but only for those with CopyToOutput) -->
100+
<!-- NOTE: None is also opt-out (must have Pack=false to exclude if IncludeNoneInPackage=true, but this property defaults to false) -->
102101
<_InferredPackageFile Include="@(_NoneWithTargetPath->'%(FullPath)')"
103102
Condition="
104103
'%(_NoneWithTargetPath.Pack)' == 'true' or
105104
'%(_NoneWithTargetPath.Kind)' != '' or
106-
('$(IncludeNoneInPackage)' == 'true' and '%(_NoneWithTargetPath.CopyToOutputDirectory)' != '' and '%(_NoneWithTargetPath.Pack)' != 'false')">
105+
('$(IncludeNoneInPackage)' == 'true' and '%(_NoneWithTargetPath.Pack)' != 'false')">
107106
<Kind Condition="'%(_NoneWithTargetPath.Kind)' == '' and '%(_NoneWithTargetPath.CopyToOutputDirectory)' != ''">$(PrimaryOutputKind)</Kind>
107+
<Kind Condition="'%(_NoneWithTargetPath.Kind)' == '' and '%(_NoneWithTargetPath.CopyToOutputDirectory)' == ''">None</Kind>
108108
</_InferredPackageFile>
109109

110110
<_InferredPackageFile Include="@(PackageReference)" Condition="'%(Identity)' != 'NuGet.Build.Packaging'">

src/Build/NuGet.Build.Packaging.Tasks/NuGet.Build.Packaging.ReferenceAssembly.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Copyright (c) .NET Foundation. All rights reserved.
4747
<ItemGroup>
4848
<PackageFile Include="@(_ReferenceAssemblyTargetPath)">
4949
<Kind>Lib</Kind>
50+
<Source>Implicit</Source>
5051
<TargetFrameworkMoniker>%(_ReferenceAssemblyTargetPath.TargetFrameworkMoniker)</TargetFrameworkMoniker>
5152
</PackageFile>
5253
</ItemGroup>

src/Build/NuGet.Build.Packaging.Tasks/NuGet.Build.Packaging.Tasks.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
<Content Include="NuGet.Build.Packaging.Authoring.targets">
4949
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
5050
</Content>
51-
<None Include="NuGet.Build.Packaging.Compatibility.props">
51+
<Content Include="NuGet.Build.Packaging.Compatibility.props">
5252
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
53-
</None>
53+
</Content>
5454
<Content Include="NuGet.Build.Packaging.ReferenceAssembly.targets">
5555
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
5656
</Content>

src/Build/NuGet.Build.Packaging.Tasks/NuGet.Build.Packaging.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Copyright (c) .NET Foundation. All rights reserved.
2626
<ApiIntersectToolExe Condition=" '$(ApiIntersectToolExe)' == '' ">ApiIntersect.exe</ApiIntersectToolExe>
2727
</PropertyGroup>
2828

29+
<ItemGroup>
30+
<AvailableItemName Include="PackageFile" />
31+
</ItemGroup>
32+
2933
<ItemDefinitionGroup>
3034
<PackageFile>
3135
<!-- See @(PackageItemKind) below -->

src/Build/NuGet.Build.Packaging.Tasks/Properties/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Build/NuGet.Build.Packaging.Tasks/Properties/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,7 @@
126126
<data name="ErrorCode_NG0012" xml:space="preserve">
127127
<value>Duplicate package source files detected for target package file path '{0}'.</value>
128128
</data>
129+
<data name="ErrorCode_NG0013" xml:space="preserve">
130+
<value>Content files cannot specify the reserved 'contentFiles' relative directory. Please use the 'CodeLanguage' and 'TargetFramework' item metadata for '{0}' to control its relative path within 'contentFiles'.</value>
131+
</data>
129132
</root>

src/Build/NuGet.Build.Packaging.Tests/AssignPackagePathTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using static NuGet.Build.Packaging.Properties.Strings;
1111
using Metadata = System.Collections.Generic.Dictionary<string, string>;
1212
using System;
13+
using NuGet.Build.Packaging.Properties;
1314

1415
namespace NuGet.Build.Packaging
1516
{
@@ -278,6 +279,34 @@ public void when_file_has_target_framework_and_tfm_then_existing_value_is_preser
278279
}));
279280
}
280281

282+
[Fact]
283+
public void when_content_is_not_framework_specific_then_has_any_lang_and_tfm()
284+
{
285+
var task = new AssignPackagePath
286+
{
287+
BuildEngine = engine,
288+
Kinds = kinds,
289+
Files = new ITaskItem[]
290+
{
291+
new TaskItem("readme.txt", new Metadata
292+
{
293+
{ "PackageId", "A" },
294+
{ "Kind", "Content" },
295+
{ "FrameworkSpecific", "false" },
296+
{ "TargetFrameworkMoniker", "MonoAndroid,Version=v2.5" },
297+
})
298+
}
299+
};
300+
301+
Assert.True(task.Execute());
302+
303+
Assert.Contains(task.AssignedFiles, item => item.Matches(new
304+
{
305+
CodeLanguage = "any",
306+
TargetFramework = "any",
307+
PackagePath = @"contentFiles\any\any\readme.txt"
308+
}));
309+
}
281310

282311
// TODO: these all end up in all lowercase, but MonoAndroid, Xamarin.iOS are usually properly
283312
// cased in nupkgs out in the wild (i.e. Rx)
@@ -440,6 +469,28 @@ public void when_assigning_content_file_then_applies_tfm_and_language(string tfm
440469
}
441470

442471
[Fact]
472+
public void when_assigning_content_file_with_reserved_dir_then_fails()
473+
{
474+
var task = new AssignPackagePath
475+
{
476+
BuildEngine = engine,
477+
Kinds = kinds,
478+
Files = new ITaskItem[]
479+
{
480+
new TaskItem(@"contentFiles\cs\monodroid\content.cs", new Metadata
481+
{
482+
{ "PackageId", "A" },
483+
{ "TargetFrameworkMoniker", ".NETFramework,Version=v4.5" },
484+
{ "TargetPath", @"contentFiles\cs\monodroid\content.cs" },
485+
{ "Kind", "Content" },
486+
})
487+
}
488+
};
489+
490+
Assert.False(task.Execute());
491+
Assert.True(engine.LoggedErrorEvents.Any(e => e.Code == nameof(Strings.ErrorCode.NG0013)));
492+
}
493+
443494
public void when_assigning_content_file_with_additional_metadata_then_preserves_metadata()
444495
{
445496
var task = new AssignPackagePath

src/Build/NuGet.Build.Packaging.Tests/NuGet.Build.Packaging.Tests.csproj

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@
4242
<Content Include="Scenarios\given_a_library_with_content\none-copy-with-include-true.txt" />
4343
<Content Include="Scenarios\given_a_library_with_content\none-copy-with-kind.txt" />
4444
<Content Include="Scenarios\given_a_library_with_content\none-with-kind.txt" />
45+
<Content Include="Scenarios\given_a_library_with_content\quickstart\any-non-tfm.txt" />
46+
<Content Include="Scenarios\given_a_library_with_content\quickstart\fs-tfm.txt" />
47+
<Content Include="Scenarios\given_a_library_with_content\quickstart\any-any.txt" />
4548
<Content Include="Scenarios\given_a_library_with_content\relative\content-copy-with-kind.txt" />
4649
<Content Include="Scenarios\given_a_library_with_content\relative\content-copy.txt" />
4750
<Content Include="Scenarios\given_a_library_with_content\relative\none-copy-with-kind.txt" />
4851
<Content Include="Scenarios\given_a_library_with_content\relative\none-copy.txt" />
52+
<None Include="Scenarios\given_a_multi_platform_solution\android\quickstart\sample.fs" />
53+
<None Include="Scenarios\given_a_multi_platform_solution\android\quickstart\sample.vb" />
4954
<Content Include="Scenarios\given_duplicate_package_files\a.csproj" />
5055
<Content Include="Scenarios\given_duplicate_package_files\b.csproj" />
5156
<Content Include="Scenarios\given_duplicate_package_files\content\content.txt" />
@@ -132,13 +137,13 @@
132137
<Compile Include="given_a_library_with_json_dependencies.cs" />
133138
<Compile Include="given_an_empty_library_project.cs" />
134139
<Compile Include="given_a_library_with_project_reference.cs" />
135-
<Content Include="Scenarios\given_a_library_with_content\contentFiles\cs\monoandroid\sample.cs" />
136-
<Content Include="Scenarios\given_a_multi_platform_solution\android\quickstart\sample.cs" />
137-
<Content Include="Scenarios\given_a_multi_platform_solution\android\quickstart\sample.fs" />
138-
<Content Include="Scenarios\given_a_multi_platform_solution\android\quickstart\sample.vb" />
139140
<Compile Include="ReadLegacyJsonDependenciesTests.cs" />
140141
<Compile Include="ReadLegacyConfigDependenciesTests.cs" />
141142
<Content Include="Scenarios\given_a_packaging_project_with_reference_assembly\b\MyClass.cs" />
143+
<Content Include="Scenarios\given_a_library_with_content\contentFiles\cs\monoandroid\content.cs" />
144+
<Content Include="Scenarios\given_a_library_with_content\contentFiles\cs\monoandroid\none.cs" />
145+
<None Include="Scenarios\given_a_multi_platform_solution\android\quickstart\sample.cs" />
146+
<Content Include="Scenarios\given_a_library_with_content\quickstart\cs-any.txt" />
142147
<Compile Include="TargetsTests.cs" />
143148
<Compile Include="UtilitiesTests.cs" />
144149
<Compile Include="Utilities\FrameworkAssemblyReferenceComparer.cs" />

src/Build/NuGet.Build.Packaging.Tests/Scenarios/given_a_library_with_content/contentFiles/cs/monoandroid/sample.cs renamed to src/Build/NuGet.Build.Packaging.Tests/Scenarios/given_a_library_with_content/contentFiles/cs/monoandroid/content.cs

File renamed without changes.

0 commit comments

Comments
 (0)