diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs index 003b21df0dad..5b4cecbcfd88 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs @@ -29,6 +29,7 @@ internal class DependencyContextBuilder private string _referenceAssembliesPath; private Dictionary _filteredPackages; private bool _includeMainProjectInDepsFile = true; + private bool _trimLibrariesWithoutAssets = true; private readonly Dictionary _dependencyLibraries; private readonly Dictionary> _libraryDependencies; private readonly List _mainProjectDependencies; @@ -212,6 +213,12 @@ public DependencyContextBuilder WithMainProjectInDepsFile(bool includeMainProjec return this; } + public DependencyContextBuilder WithTrimLibrariesWithoutAssets(bool trimLibrariesWithoutAssets) + { + _trimLibrariesWithoutAssets = trimLibrariesWithoutAssets; + return this; + } + public DependencyContextBuilder WithRuntimePackAssets(IEnumerable runtimePackAssets) { _runtimePackAssets = new Dictionary>(); @@ -356,31 +363,43 @@ public DependencyContext Build(string[] userRuntimeAssemblies = null) } } - var unprocessedLibraries = runtimeLibraries.ToHashSet(); - while (unprocessedLibraries.Any()) + if (_trimLibrariesWithoutAssets) { - var lib = unprocessedLibraries.First(); - unprocessedLibraries.Remove(lib); - - if (lib.Library.RuntimeAssemblyGroups.Count == 0 && lib.Library.NativeLibraryGroups.Count == 0 && lib.Library.ResourceAssemblies.Count == 0) + var unprocessedLibraries = runtimeLibraries.ToHashSet(); + while (unprocessedLibraries.Any()) { - if (lib.Library.Dependencies.All(d => !libraries.TryGetValue(d.Name, out var dependency) || dependency.Dependents.Count > 1)) + var lib = unprocessedLibraries.First(); + unprocessedLibraries.Remove(lib); + + if (lib.Library.Name.Equals("xunit", StringComparison.OrdinalIgnoreCase) || + lib.Library.Name.Equals("xunit.core", StringComparison.OrdinalIgnoreCase)) { - runtimeLibraries.Remove(lib); - libraries.Remove(lib.Library.Name); - foreach (var dependency in lib.Library.Dependencies) + // Special case xunit and xunit.core, they should not be removed because the xUnit v2 runner looks for these libraries in the deps.json to + // identify test projects. + // See https://github.com/dotnet/sdk/issues/49248 + continue; + } + + if (lib.Library.RuntimeAssemblyGroups.Count == 0 && lib.Library.NativeLibraryGroups.Count == 0 && lib.Library.ResourceAssemblies.Count == 0) + { + if (lib.Library.Dependencies.All(d => !libraries.TryGetValue(d.Name, out var dependency) || dependency.Dependents.Count > 1)) { - if (libraries.TryGetValue(dependency.Name, out ModifiableRuntimeLibrary value)) + runtimeLibraries.Remove(lib); + libraries.Remove(lib.Library.Name); + foreach (var dependency in lib.Library.Dependencies) { - value.Dependents.Remove(lib.Library.Name); + if (libraries.TryGetValue(dependency.Name, out ModifiableRuntimeLibrary value)) + { + value.Dependents.Remove(lib.Library.Name); + } } - } - foreach (var dependent in lib.Dependents) - { - if (libraries.TryGetValue(dependent, out var dep)) + foreach (var dependent in lib.Dependents) { - unprocessedLibraries.Add(dep); + if (libraries.TryGetValue(dependent, out var dep)) + { + unprocessedLibraries.Add(dep); + } } } } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs index 1fe61c8b33ef..8b89525c9fd1 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs @@ -49,6 +49,8 @@ public class GenerateDepsFile : TaskBase [Required] public bool IncludeMainProject { get; set; } + public bool TrimDepsJsonLibrariesWithoutAssets { get; set; } + // @(ReferencePath) that will be passed to public ITaskItem[] ReferencePaths { get; set; } = Array.Empty(); @@ -230,6 +232,7 @@ bool ShouldIncludeRuntimeAsset(ITaskItem item) builder = builder .WithMainProjectInDepsFile(IncludeMainProject) + .WithTrimLibrariesWithoutAssets(TrimDepsJsonLibrariesWithoutAssets) .WithReferenceAssemblies(referenceAssemblyInfos) .WithDirectReferences(directReferences) .WithDependencyReferences(dependencyReferences) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets index ad58fc444253..bf7a6812dea2 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DesignerSupport.targets @@ -59,6 +59,7 @@ Copyright (c) .NET Foundation. All rights reserved. AssetsFilePath="$(ProjectAssetsFile)" DepsFilePath="$(_DesignerDepsFilePath)" IncludeMainProject="false" + TrimDepsJsonLibrariesWithoutAssets="$(TrimDepsJsonLibrariesWithoutAssets)" IncludeRuntimeFileVersions="$(IncludeFileVersionsInDependencyFile)" IsSelfContained="false" PlatformLibraryName="$(MicrosoftNETPlatformLibrary)" diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets index cdb8d81d532f..19d27313cbac 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets @@ -1248,6 +1248,7 @@ Copyright (c) .NET Foundation. All rights reserved. ReferenceAssemblies="@(_ReferenceAssemblies)" RuntimePackAssets="@(RuntimePackAsset)" IncludeMainProject="$(IncludeMainProjectInDepsFile)" + TrimDepsJsonLibrariesWithoutAssets="$(TrimDepsJsonLibrariesWithoutAssets)" RuntimeIdentifier="$(RuntimeIdentifier)" PlatformLibraryName="$(MicrosoftNETPlatformLibrary)" RuntimeFrameworks="@(RuntimeFramework)" diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets index e7278514a2a4..0457adcf0704 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets @@ -88,6 +88,7 @@ Copyright (c) .NET Foundation. All rights reserved. $(TargetDir)$(ProjectRuntimeConfigFileName) $(TargetDir)$(AssemblyName).runtimeconfig.dev.json true + true @@ -316,6 +317,7 @@ Copyright (c) .NET Foundation. All rights reserved. ReferenceAssemblies="@(_ReferenceAssemblies)" RuntimePackAssets="@(RuntimePackAsset)" IncludeMainProject="$(IncludeMainProjectInDepsFile)" + TrimDepsJsonLibrariesWithoutAssets="$(TrimDepsJsonLibrariesWithoutAssets)" RuntimeIdentifier="$(RuntimeIdentifier)" PlatformLibraryName="$(MicrosoftNETPlatformLibrary)" RuntimeFrameworks="@(RuntimeFramework)" diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildADesktopLibrary.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildADesktopLibrary.cs index 0761f7c793f0..86d8da4dd6b5 100644 --- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildADesktopLibrary.cs +++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildADesktopLibrary.cs @@ -136,8 +136,10 @@ public void PackageReferences_with_private_assets_do_not_appear_in_deps_file(str } } - [Fact] - public void PackageWithoutAssets_ShouldNotShowUpInDepsJson() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void PackageWithoutAssets_ShouldNotShowUpInDepsJson(bool trimLibrariesWithoutAssets) { var testProject = new TestProject() { @@ -145,15 +147,49 @@ public void PackageWithoutAssets_ShouldNotShowUpInDepsJson() }; testProject.PackageReferences.Add(new TestPackageReference("Nerdbank.GitVersioning", "3.6.146")); + if (!trimLibrariesWithoutAssets) + { + testProject.AdditionalProperties["TrimDepsJsonLibrariesWithoutAssets"] = "False"; + } + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var buildCommand = new BuildCommand(testAsset); + buildCommand.Execute().Should().Pass(); + + using (var depsJsonFileStream = File.OpenRead(Path.Combine(buildCommand.GetOutputDirectory(ToolsetInfo.CurrentTargetFramework).FullName, $"{testProject.Name}.deps.json"))) + { + var dependencyContext = new DependencyContextJsonReader().Read(depsJsonFileStream); + if (trimLibrariesWithoutAssets) + { + dependencyContext.RuntimeLibraries.Any(l => l.Name.Equals("Nerdbank.GitVersioning")).Should().BeFalse(); + } + else + { + dependencyContext.RuntimeLibraries.Any(l => l.Name.Equals("Nerdbank.GitVersioning")).Should().BeTrue(); + } + } + } + + [Fact] + public void XUnitCoreIsNotTrimmed() + { + // Regression test for https://github.com/dotnet/sdk/issues/49248 + + var testProject = new TestProject(); + testProject.PackageReferences.Add(new TestPackageReference("xunit.core", "2.9.3")); + testProject.PackageReferences.Add(new TestPackageReference("xunit.extensibility.core", "2.9.3")); + testProject.PackageReferences.Add(new TestPackageReference("xunit.extensibility.execution", "2.9.3")); + var testAsset = _testAssetsManager.CreateTestProject(testProject); var buildCommand = new BuildCommand(testAsset); buildCommand.Execute().Should().Pass(); - using (var depsJsonFileStream = File.OpenRead(Path.Combine(buildCommand.GetOutputDirectory(ToolsetInfo.CurrentTargetFramework).FullName, "PackageWithoutAssets_ShouldNotShowUpInDepsJson.deps.json"))) + using (var depsJsonFileStream = File.OpenRead(Path.Combine(buildCommand.GetOutputDirectory(ToolsetInfo.CurrentTargetFramework).FullName, $"{testProject.Name}.deps.json"))) { var dependencyContext = new DependencyContextJsonReader().Read(depsJsonFileStream); - dependencyContext.RuntimeLibraries.Any(l => l.Name.Equals("Nerdbank.GitVersioning")).Should().BeFalse(); + dependencyContext.RuntimeLibraries.Any(l => l.Name.Equals("xunit.core")).Should().BeTrue(); } }