diff --git a/readme.md b/readme.md index 671e8c87..537baed8 100644 --- a/readme.md +++ b/readme.md @@ -187,9 +187,9 @@ The basic item metadata that drive pack inference are: 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: -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. -b. **FrameworkSpecific**: *true*/*false*, determines whether the project's target framework is used when building the final *PackagePath*. -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. + * **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. + * **FrameworkSpecific**: *true*/*false*, determines whether the project's target framework is used when building the final *PackagePath*. + * **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. 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 ### PackageReference -Package references are turned into package dependencies by default (essentially converting `` to ``), 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). +Package references are turned into package dependencies by default (essentially converting `` to ``), 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). -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. +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. 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`. As usual, you can change this default behavior by using `Pack=false` metadata. +You can also control precisely what assets are surfaced from your dependencies, by +using `PackInclude` and `PackExclude` metadata on the `PackageReference`. This will +result in the corresponding `include`/`exclude` attributes as documented in the +[nuspec reference](https://learn.microsoft.com/en-us/nuget/reference/nuspec#dependencies-element). If not defined, both are defaulted to the package +reference `IncludeAssets` and `ExcludeAssets` metadata. + ### ProjectReference 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. diff --git a/src/NuGetizer.Tasks/CreatePackage.cs b/src/NuGetizer.Tasks/CreatePackage.cs index 4d4b9d9a..b43e2e5e 100644 --- a/src/NuGetizer.Tasks/CreatePackage.cs +++ b/src/NuGetizer.Tasks/CreatePackage.cs @@ -42,7 +42,8 @@ public class CreatePackage : Task public override bool Execute() { - if (Environment.GetEnvironmentVariable("DEBUG_NUGETIZER") == "1") + if (Environment.GetEnvironmentVariable("DEBUG_NUGETIZER") == "1" || + Environment.GetEnvironmentVariable("DEBUG_NUGETIZER_PACK") == "1") Debugger.Launch(); try @@ -244,8 +245,8 @@ where PackFolderKind.Dependency.Equals(item.GetMetadata(MetadataName.PackFolder) Id = item.ItemSpec, Version = VersionRange.Parse(item.GetMetadata(MetadataName.Version)), TargetFramework = item.GetNuGetTargetFramework(), - Include = item.GetNullableMetadata(MetadataName.IncludeAssets), - Exclude = item.GetNullableMetadata(MetadataName.ExcludeAssets) + Include = item.GetNullableMetadata(MetadataName.PackInclude) ?? item.GetNullableMetadata(MetadataName.IncludeAssets), + Exclude = item.GetNullableMetadata(MetadataName.PackExclude) ?? item.GetNullableMetadata(MetadataName.ExcludeAssets) }; var definedDependencyGroups = (from dependency in dependencies diff --git a/src/NuGetizer.Tasks/MetadataName.cs b/src/NuGetizer.Tasks/MetadataName.cs index 21dd612e..96f846e5 100644 --- a/src/NuGetizer.Tasks/MetadataName.cs +++ b/src/NuGetizer.Tasks/MetadataName.cs @@ -30,10 +30,28 @@ public static class MetadataName /// public const string PrivateAssets = nameof(PrivateAssets); + /// + /// Assets to include for a dependency/package reference + /// public const string IncludeAssets = nameof(IncludeAssets); + /// + /// Assets to exclude for a dependency/package reference + /// public const string ExcludeAssets = nameof(ExcludeAssets); + /// + /// Same as , but allows having a different value for the + /// included assets in pack vs build/restore of the referencing project. + /// + public const string PackInclude = nameof(PackInclude); + + /// + /// Same as , but allows having a different value for the + /// excluded assets in pack vs build/restore of the referencing project. + /// + public const string PackExclude = nameof(PackExclude); + /// /// Whether the project can be packed as a .nupkg. /// diff --git a/src/NuGetizer.Tests/CreatePackageTests.cs b/src/NuGetizer.Tests/CreatePackageTests.cs index 95ea9e36..bd8c8444 100644 --- a/src/NuGetizer.Tests/CreatePackageTests.cs +++ b/src/NuGetizer.Tests/CreatePackageTests.cs @@ -330,6 +330,58 @@ public void when_creating_package_with_dependency_and_without_exclude_assets_the Assert.Equal(0, manifest.Metadata.DependencyGroups.First().Packages.First().Exclude.Count); } + [Fact] + public void when_creating_package_with_dependency_packinclude_then_contains_dependency_include_attribute() + { + task.Contents = new[] + { + new TaskItem("Newtonsoft.Json", new Metadata + { + { MetadataName.PackageId, task.Manifest.GetMetadata("Id") }, + { MetadataName.PackFolder, PackFolderKind.Dependency }, + { MetadataName.Version, "8.0.0" }, + // NOTE: AssignPackagePath takes care of converting TFM > short name + { MetadataName.TargetFramework, "net472" }, + { MetadataName.PackInclude, "build" } + }), + }; + + var manifest = ExecuteTask(); + + Assert.NotNull(manifest); + Assert.Single(manifest.Metadata.DependencyGroups); + Assert.Single(manifest.Metadata.DependencyGroups.First().Packages); + Assert.Equal("Newtonsoft.Json", manifest.Metadata.DependencyGroups.First().Packages.First().Id); + Assert.Equal(1, manifest.Metadata.DependencyGroups.First().Packages.First().Include.Count); + Assert.Equal("build", manifest.Metadata.DependencyGroups.First().Packages.First().Include[0]); + } + + [Fact] + public void when_creating_package_with_dependency_packexclude_assets_then_contains_dependency_exclude_attribute() + { + task.Contents = new[] + { + new TaskItem("Newtonsoft.Json", new Metadata + { + { MetadataName.PackageId, task.Manifest.GetMetadata("Id") }, + { MetadataName.PackFolder, PackFolderKind.Dependency }, + { MetadataName.Version, "8.0.0" }, + // NOTE: AssignPackagePath takes care of converting TFM > short name + { MetadataName.TargetFramework, "net472" }, + { MetadataName.PackExclude, "build" } + }), + }; + + var manifest = ExecuteTask(); + + Assert.NotNull(manifest); + Assert.Single(manifest.Metadata.DependencyGroups); + Assert.Single(manifest.Metadata.DependencyGroups.First().Packages); + Assert.Equal("Newtonsoft.Json", manifest.Metadata.DependencyGroups.First().Packages.First().Id); + Assert.Equal(1, manifest.Metadata.DependencyGroups.First().Packages.First().Exclude.Count); + Assert.Equal("build", manifest.Metadata.DependencyGroups.First().Packages.First().Exclude[0]); + } + [Fact] public void when_creating_package_with_non_framework_secific_dependency_then_contains_generic_dependency_group() { diff --git a/src/NuGetizer.Tests/InlineProjectTests.cs b/src/NuGetizer.Tests/InlineProjectTests.cs index fe356ad7..175fb11f 100644 --- a/src/NuGetizer.Tests/InlineProjectTests.cs +++ b/src/NuGetizer.Tests/InlineProjectTests.cs @@ -722,11 +722,8 @@ public void when_packing_with_refs_then_includes_runtime_libs_for_private() """ - Exe - net472 - True - TestNuGetizer - Latest + true + net472 @@ -747,5 +744,35 @@ public void when_packing_with_refs_then_includes_runtime_libs_for_private() PathInPackage = "lib/net461/System.Memory.dll", })); } + + [Fact] + public void when_packing_dependencies_then_can_include_exclude_assets() + { + var result = Builder.BuildProject( + """ + + + Exe + net472 + true + Latest + + + + + + + """, output: output); + + result.AssertSuccess(output); + + Assert.Contains(result.Items, item => item.Matches(new + { + Identity = "Devlooped.SponsorLink", + PackFolder = "Dependency", + IncludeAssets = "analyzers", + ExcludeAssets = "build" + })); + } } }