Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Include/Exclude attributes from Dependency #285

Merged
merged 1 commit into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 `<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).
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).

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.
Expand Down
7 changes: 4 additions & 3 deletions src/NuGetizer.Tasks/CreatePackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions src/NuGetizer.Tasks/MetadataName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,28 @@ public static class MetadataName
/// </summary>
public const string PrivateAssets = nameof(PrivateAssets);

/// <summary>
/// Assets to include for a dependency/package reference
/// </summary>
public const string IncludeAssets = nameof(IncludeAssets);

/// <summary>
/// Assets to exclude for a dependency/package reference
/// </summary>
public const string ExcludeAssets = nameof(ExcludeAssets);

/// <summary>
/// Same as <see cref="IncludeAssets"/>, but allows having a different value for the
/// included assets in pack vs build/restore of the referencing project.
/// </summary>
public const string PackInclude = nameof(PackInclude);

/// <summary>
/// Same as <see cref="ExcludeAssets"/>, but allows having a different value for the
/// excluded assets in pack vs build/restore of the referencing project.
/// </summary>
public const string PackExclude = nameof(PackExclude);

/// <summary>
/// Whether the project can be packed as a .nupkg.
/// </summary>
Expand Down
52 changes: 52 additions & 0 deletions src/NuGetizer.Tests/CreatePackageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
37 changes: 32 additions & 5 deletions src/NuGetizer.Tests/InlineProjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -722,11 +722,8 @@ public void when_packing_with_refs_then_includes_runtime_libs_for_private()
"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>TestNuGetizer</PackageId>
<LangVersion>Latest</LangVersion>
<IsPackable>true</IsPackable>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand All @@ -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(
"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
<IsPackable>true</IsPackable>
<LangVersion>Latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Devlooped.SponsorLink" Version="0.9.2" IncludeAssets="analyzers" ExcludeAssets="build" />
</ItemGroup>
</Project>
""", output: output);

result.AssertSuccess(output);

Assert.Contains(result.Items, item => item.Matches(new
{
Identity = "Devlooped.SponsorLink",
PackFolder = "Dependency",
IncludeAssets = "analyzers",
ExcludeAssets = "build"
}));
}
}
}