Skip to content

Commit a5ed5e6

Browse files
committed
Support Include/Exclude attributes from Dependency
We currently surface the PackageReference's IncludeAssets/ExcludeAssets as the ultimate dependency Include/Exclude metadata attributes. This is not always what you want, though. For example: if you need the compile dependency for compiling the project, but only want to surface the dependency as a build dependency, you cannot exclude the compile asset because your project depends on the lib to build. In order to make this more intuitive and aligned with the other attributes, we introduce PackInclude/PackExclude attributes for that purpose, which map exactly to the .nuspec: https://learn.microsoft.com/en-us/nuget/reference/nuspec#dependencies-element. Note that the existing behavior will be overriden if those attributes are provided, which provides backwards compatibility.
1 parent cda0531 commit a5ed5e6

File tree

5 files changed

+117
-13
lines changed

5 files changed

+117
-13
lines changed

readme.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,9 @@ The basic item metadata that drive pack inference are:
187187

188188
If the item does **not** provide a *PackagePath*, and *Pack* is not *false*, the inference targets wil try to determine the right value, based on the following additional metadata:
189189

190-
a. **PackFolder**: typically one of the [built-in package folders](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/PackagingConstants.cs#L19), such as *build*, *lib*, etc.
191-
b. **FrameworkSpecific**: *true*/*false*, determines whether the project's target framework is used when building the final *PackagePath*.
192-
c. **TargetPath**: optional PackFolder-relative path for the item. If not provided, the relative path of the item in the project (or its *Link* metadata) is used.
190+
* **PackFolder**: typically one of the [built-in package folders](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/PackagingConstants.cs#L19), such as *build*, *lib*, etc.
191+
* **FrameworkSpecific**: *true*/*false*, determines whether the project's target framework is used when building the final *PackagePath*.
192+
* **TargetPath**: optional PackFolder-relative path for the item. If not provided, the relative path of the item in the project (or its *Link* metadata) is used.
193193

194194

195195
When an item specifies *FrameworkSpecific=true*, the project's target framework is added to the final package path, such as `lib\netstandard2.0\My.dll`. Since the package folder itself typically determines whether it contains framework-specific files or not, the *FrameworkSpecific* value has sensible defaults so you don't have to specify it unless you want to override it. The [default values from NuGetizer.props](src/NuGetizer.Tasks/NuGetizer.props) are:
@@ -284,14 +284,20 @@ In addition, the resulting `PackageFile` items for these items point to the loca
284284

285285
### PackageReference
286286

287-
Package references are turned into package dependencies by default (essentially converting `<PackageReference>` to `<PackageFile ... PackFolder="Dependency">`), unless `PackDependencies` property is `false`. If the package reference specifies `PrivateAssets="all"`, however, it's not added as a dependency. Instead, in that case, all the files contributed to the compilation are placed in the same `PackFolder` as the project's build output (if packable, depending on `PackBuildOutput` property).
287+
Package references are turned into package dependencies by default (essentially converting `<PackageReference>` to `<PackageFile ... PackFolder="Dependency">`), unless `PackDependencies` property is `false`. If the package reference specifies `PrivateAssets="all"`, however, it's not added as a dependency. Instead, in that case, all the files contributed to the compilation (more precisely: all copy-local runtime dependencies) are placed in the same `PackFolder` as the project's build output (if packable, depending on `PackBuildOutput` property).
288288

289-
Build-only dependencies that don't contribute assemblies to the output (i.e. analyzers or things like [GitInfo](https://github.com/kzu/GitInfo) or [ThisAssembly](https://github.com/kzu/ThisAssembly) won't cause any extra items.
289+
Build-only dependencies that don't contribute assemblies to the output (i.e. analyzers or things like [GitInfo](https://github.com/devlooped/GitInfo) or [ThisAssembly](https://github.com/devlooped/ThisAssembly) won't cause any extra items.
290290

291291
This even works transitively, so if you use *PrivateAssets=all* on package reference *A*, which in turn has a package dependency on *B* and *B* in turn depends on *C*, all of *A*, *B* and *C* assets will be packed. You can opt out of the transitive packing with `PackTransitive=false` metadata on the `PackageReference`.
292292

293293
As usual, you can change this default behavior by using `Pack=false` metadata.
294294

295+
You can also control precisely what assets are surfaced from your dependencies, by
296+
using `PackInclude` and `PackExclude` metadata on the `PackageReference`. This will
297+
result in the corresponding `include`/`exclude` attributes as documented in the
298+
[nuspec reference](https://learn.microsoft.com/en-us/nuget/reference/nuspec#dependencies-element). If not defined, both are defaulted to the package
299+
reference `IncludeAssets` and `ExcludeAssets` metadata.
300+
295301
### ProjectReference
296302

297303
Unlike SDK Pack that [considers project references as package references by default](https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#project-to-project-references), NuGetizer has an explicit contract between projects: the `GetPackageContents` target. This target is invoked when packing project references, and it returns whatever the referenced project exposes as package contents (including the inference rules above). If the project is *packable* (that is, it produces a package, denoted by the presence of a `PackageId` property or `IsPackable=true`, for compatibility with SDK Pack), it will be packed as a dependency/package reference instead.

src/NuGetizer.Tasks/CreatePackage.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public class CreatePackage : Task
4242

4343
public override bool Execute()
4444
{
45-
if (Environment.GetEnvironmentVariable("DEBUG_NUGETIZER") == "1")
45+
if (Environment.GetEnvironmentVariable("DEBUG_NUGETIZER") == "1" ||
46+
Environment.GetEnvironmentVariable("DEBUG_NUGETIZER_PACK") == "1")
4647
Debugger.Launch();
4748

4849
try
@@ -244,8 +245,8 @@ where PackFolderKind.Dependency.Equals(item.GetMetadata(MetadataName.PackFolder)
244245
Id = item.ItemSpec,
245246
Version = VersionRange.Parse(item.GetMetadata(MetadataName.Version)),
246247
TargetFramework = item.GetNuGetTargetFramework(),
247-
Include = item.GetNullableMetadata(MetadataName.IncludeAssets),
248-
Exclude = item.GetNullableMetadata(MetadataName.ExcludeAssets)
248+
Include = item.GetNullableMetadata(MetadataName.PackInclude) ?? item.GetNullableMetadata(MetadataName.IncludeAssets),
249+
Exclude = item.GetNullableMetadata(MetadataName.PackExclude) ?? item.GetNullableMetadata(MetadataName.ExcludeAssets)
249250
};
250251

251252
var definedDependencyGroups = (from dependency in dependencies

src/NuGetizer.Tasks/MetadataName.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,28 @@ public static class MetadataName
3030
/// </summary>
3131
public const string PrivateAssets = nameof(PrivateAssets);
3232

33+
/// <summary>
34+
/// Assets to include for a dependency/package reference
35+
/// </summary>
3336
public const string IncludeAssets = nameof(IncludeAssets);
3437

38+
/// <summary>
39+
/// Assets to exclude for a dependency/package reference
40+
/// </summary>
3541
public const string ExcludeAssets = nameof(ExcludeAssets);
3642

43+
/// <summary>
44+
/// Same as <see cref="IncludeAssets"/>, but allows having a different value for the
45+
/// included assets in pack vs build/restore of the referencing project.
46+
/// </summary>
47+
public const string PackInclude = nameof(PackInclude);
48+
49+
/// <summary>
50+
/// Same as <see cref="ExcludeAssets"/>, but allows having a different value for the
51+
/// excluded assets in pack vs build/restore of the referencing project.
52+
/// </summary>
53+
public const string PackExclude = nameof(PackExclude);
54+
3755
/// <summary>
3856
/// Whether the project can be packed as a .nupkg.
3957
/// </summary>

src/NuGetizer.Tests/CreatePackageTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,58 @@ public void when_creating_package_with_dependency_and_without_exclude_assets_the
330330
Assert.Equal(0, manifest.Metadata.DependencyGroups.First().Packages.First().Exclude.Count);
331331
}
332332

333+
[Fact]
334+
public void when_creating_package_with_dependency_packinclude_then_contains_dependency_include_attribute()
335+
{
336+
task.Contents = new[]
337+
{
338+
new TaskItem("Newtonsoft.Json", new Metadata
339+
{
340+
{ MetadataName.PackageId, task.Manifest.GetMetadata("Id") },
341+
{ MetadataName.PackFolder, PackFolderKind.Dependency },
342+
{ MetadataName.Version, "8.0.0" },
343+
// NOTE: AssignPackagePath takes care of converting TFM > short name
344+
{ MetadataName.TargetFramework, "net472" },
345+
{ MetadataName.PackInclude, "build" }
346+
}),
347+
};
348+
349+
var manifest = ExecuteTask();
350+
351+
Assert.NotNull(manifest);
352+
Assert.Single(manifest.Metadata.DependencyGroups);
353+
Assert.Single(manifest.Metadata.DependencyGroups.First().Packages);
354+
Assert.Equal("Newtonsoft.Json", manifest.Metadata.DependencyGroups.First().Packages.First().Id);
355+
Assert.Equal(1, manifest.Metadata.DependencyGroups.First().Packages.First().Include.Count);
356+
Assert.Equal("build", manifest.Metadata.DependencyGroups.First().Packages.First().Include[0]);
357+
}
358+
359+
[Fact]
360+
public void when_creating_package_with_dependency_packexclude_assets_then_contains_dependency_exclude_attribute()
361+
{
362+
task.Contents = new[]
363+
{
364+
new TaskItem("Newtonsoft.Json", new Metadata
365+
{
366+
{ MetadataName.PackageId, task.Manifest.GetMetadata("Id") },
367+
{ MetadataName.PackFolder, PackFolderKind.Dependency },
368+
{ MetadataName.Version, "8.0.0" },
369+
// NOTE: AssignPackagePath takes care of converting TFM > short name
370+
{ MetadataName.TargetFramework, "net472" },
371+
{ MetadataName.PackExclude, "build" }
372+
}),
373+
};
374+
375+
var manifest = ExecuteTask();
376+
377+
Assert.NotNull(manifest);
378+
Assert.Single(manifest.Metadata.DependencyGroups);
379+
Assert.Single(manifest.Metadata.DependencyGroups.First().Packages);
380+
Assert.Equal("Newtonsoft.Json", manifest.Metadata.DependencyGroups.First().Packages.First().Id);
381+
Assert.Equal(1, manifest.Metadata.DependencyGroups.First().Packages.First().Exclude.Count);
382+
Assert.Equal("build", manifest.Metadata.DependencyGroups.First().Packages.First().Exclude[0]);
383+
}
384+
333385
[Fact]
334386
public void when_creating_package_with_non_framework_secific_dependency_then_contains_generic_dependency_group()
335387
{

src/NuGetizer.Tests/InlineProjectTests.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -722,11 +722,8 @@ public void when_packing_with_refs_then_includes_runtime_libs_for_private()
722722
"""
723723
<Project Sdk="Microsoft.NET.Sdk">
724724
<PropertyGroup>
725-
<OutputType>Exe</OutputType>
726-
<TargetFramework>net472</TargetFramework>
727-
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
728-
<PackageId>TestNuGetizer</PackageId>
729-
<LangVersion>Latest</LangVersion>
725+
<IsPackable>true</IsPackable>
726+
<TargetFramework>netstandard2.0</TargetFramework>
730727
</PropertyGroup>
731728
732729
<ItemGroup>
@@ -747,5 +744,35 @@ public void when_packing_with_refs_then_includes_runtime_libs_for_private()
747744
PathInPackage = "lib/net461/System.Memory.dll",
748745
}));
749746
}
747+
748+
[Fact]
749+
public void when_packing_dependencies_then_can_include_exclude_assets()
750+
{
751+
var result = Builder.BuildProject(
752+
"""
753+
<Project Sdk="Microsoft.NET.Sdk">
754+
<PropertyGroup>
755+
<OutputType>Exe</OutputType>
756+
<TargetFramework>net472</TargetFramework>
757+
<IsPackable>true</IsPackable>
758+
<LangVersion>Latest</LangVersion>
759+
</PropertyGroup>
760+
761+
<ItemGroup>
762+
<PackageReference Include="Devlooped.SponsorLink" Version="0.9.2" IncludeAssets="analyzers" ExcludeAssets="build" />
763+
</ItemGroup>
764+
</Project>
765+
""", output: output);
766+
767+
result.AssertSuccess(output);
768+
769+
Assert.Contains(result.Items, item => item.Matches(new
770+
{
771+
Identity = "Devlooped.SponsorLink",
772+
PackFolder = "Dependency",
773+
IncludeAssets = "analyzers",
774+
ExcludeAssets = "build"
775+
}));
776+
}
750777
}
751778
}

0 commit comments

Comments
 (0)