diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8de744c8301a..00824e505453 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -125,9 +125,9 @@ https://github.com/microsoft/vstest b1452a516a80672fa814f4ebdf2151c0875d0b20 - + https://github.com/dotnet/linker - ef2d0f25b72469b55925251a79f12bcbf98644bf + b6f1fb4d67f6aab530382e1973b898ba858709e0 @@ -135,9 +135,9 @@ 206dccb7945aaa3f26599fbe742de9022ca7ef91 - + https://github.com/dotnet/linker - ef2d0f25b72469b55925251a79f12bcbf98644bf + b6f1fb4d67f6aab530382e1973b898ba858709e0 https://github.com/dotnet/runtime diff --git a/eng/Versions.props b/eng/Versions.props index 9dc41a46869f..283931ef70a3 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -83,7 +83,7 @@ - 7.0.100-1.22354.1 + 7.0.100-1.22357.2 $(MicrosoftNETILLinkTasksPackageVersion) diff --git a/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props b/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props index 58ee0bcdac04..b7f228be4019 100644 --- a/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props +++ b/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props @@ -29,7 +29,7 @@ Copyright (c) .NET Foundation. All rights reserved. true - link + partial false diff --git a/src/Tests/Microsoft.NET.Build.Tests/ReferenceExeTests.cs b/src/Tests/Microsoft.NET.Build.Tests/ReferenceExeTests.cs index ee2195b22e6c..940b231fa79e 100644 --- a/src/Tests/Microsoft.NET.Build.Tests/ReferenceExeTests.cs +++ b/src/Tests/Microsoft.NET.Build.Tests/ReferenceExeTests.cs @@ -29,6 +29,10 @@ public ReferenceExeTests(ITestOutputHelper log) : base(log) private bool TestWithPublish { get; set; } = false; + private bool PublishTrimmed = false; + + private bool ReferenceExeInCode = false; + private TestProject MainProject { get; set; } private TestProject ReferencedProject { get; set; } @@ -44,10 +48,31 @@ private void CreateProjects() }; MainProject.PackageReferences.Add(new TestPackageReference("Humanizer", "2.8.26")); - MainProject.SourceFiles["Program.cs"] = @"using Humanizer; System.Console.WriteLine(""MainProject"".Humanize());"; + var mainProjectSrc = @" +using System; +using Humanizer; +Console.WriteLine(""MainProject"".Humanize());"; + + if (PublishTrimmed) + { + MainProject.AdditionalProperties["PublishTrimmed"] = "true"; + + // If we're fully trimming, unless the trimmed project contains an explicit reference in code + // to the referenced project, it will get trimmed away + if (ReferenceExeInCode) + { + mainProjectSrc += @" +// Always false, but the trimmer doesn't know that +if (string.Empty.Length > 0) +{ + ReferencedExeProgram.Main(); +}"; + + } + } - // By default we don't create the app host on Mac for FDD. For these tests, we want to create it everywhere - MainProject.AdditionalProperties["UseAppHost"] = "true"; + + MainProject.SourceFiles["Program.cs"] = mainProjectSrc; if (MainSelfContained) { @@ -62,8 +87,6 @@ private void CreateProjects() IsExe = true, }; - ReferencedProject.AdditionalProperties["UseAppHost"] = "true"; - if (ReferencedSelfContained) { ReferencedProject.RuntimeIdentifier = EnvironmentInfo.GetCompatibleRid(); @@ -71,7 +94,15 @@ private void CreateProjects() // Use a lower version of a library in the referenced project ReferencedProject.PackageReferences.Add(new TestPackageReference("Humanizer", "2.7.9")); - ReferencedProject.SourceFiles["Program.cs"] = @"using Humanizer; System.Console.WriteLine(""ReferencedProject"".Humanize());"; + ReferencedProject.SourceFiles["Program.cs"] = @" +using Humanizer; +public class ReferencedExeProgram +{ + public static void Main() + { + System.Console.WriteLine(""ReferencedProject"".Humanize()); + } +}"; MainProject.ReferencedProjects.Add(ReferencedProject); } @@ -122,11 +153,23 @@ private void RunTest(string buildFailureCode = null, [CallerMemberName] string c var referencedExeResult = new RunExeCommand(Log, referencedExePath) .Execute(); - referencedExeResult - .Should() - .Pass() - .And - .HaveStdOut("Referenced project"); + // If we're trimming and didn't reference the exe in source we would expect it to be trimmed from the output + if (PublishTrimmed && !ReferenceExeInCode) + { + referencedExeResult + .Should() + .Fail() + .And + .HaveStdErrContaining("The application to execute does not exist"); + } + else + { + referencedExeResult + .Should() + .Pass() + .And + .HaveStdOut("Referenced project"); + } } else { @@ -238,25 +281,20 @@ public void ReferencedExeCanRunWhenPublished(bool selfContained) RunTest(); } - [Fact] - public void ReferencedExeCanRunWhenPublishedWithTrimming() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ReferencedExeCanRunWhenPublishedWithTrimming(bool referenceExeInCode) { MainSelfContained = true; ReferencedSelfContained = true; TestWithPublish = true; + PublishTrimmed = true; + ReferenceExeInCode = referenceExeInCode; CreateProjects(); - if (MainSelfContained) - { - MainProject.AdditionalProperties["PublishTrimmed"] = "True"; - } - if (ReferencedSelfContained) - { - ReferencedProject.AdditionalProperties["PublishTrimmed"] = "True"; - } - RunTest(); } diff --git a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs index d4bd36d1d608..449e626529ce 100644 --- a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs +++ b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs @@ -23,6 +23,7 @@ using Xunit; using Xunit.Abstractions; using static Microsoft.NET.Publish.Tests.PublishTestUtils; +using System.Security.Permissions; namespace Microsoft.NET.Publish.Tests { @@ -187,6 +188,10 @@ public void PrepareForILLink_can_set_TrimMode(string targetFramework) [RequiresMSBuildVersionTheory("17.0.0.32901")] [InlineData("net5.0", "link")] [InlineData(ToolsetInfo.CurrentTargetFramework, "copyused")] + [InlineData("net6.0", "full")] + [InlineData(ToolsetInfo.CurrentTargetFramework, "full")] + [InlineData("net6.0", "partial")] + [InlineData(ToolsetInfo.CurrentTargetFramework, "partial")] public void ILLink_respects_global_TrimMode(string targetFramework, string trimMode) { var projectName = "HelloWorld"; @@ -210,7 +215,7 @@ public void ILLink_respects_global_TrimMode(string targetFramework, string trimM File.Exists(publishedDll).Should().BeTrue(); File.Exists(isTrimmableDll).Should().BeTrue(); DoesImageHaveMethod(isTrimmableDll, "UnusedMethodToRoot").Should().BeTrue(); - if (trimMode == "link") { + if (trimMode is "link" or "full" or "partial") { // Check that the assembly was trimmed at the member level DoesImageHaveMethod(isTrimmableDll, "UnusedMethod").Should().BeFalse(); } else { @@ -269,7 +274,8 @@ public void ILLink_respects_TrimmableAssembly(string targetFramework) } [RequiresMSBuildVersionTheory("17.0.0.32901")] - [MemberData(nameof(Net6Plus), MemberType = typeof(PublishTestUtils))] + [InlineData("net6.0")] + [InlineData("net7.0")] public void ILLink_respects_IsTrimmable_attribute(string targetFramework) { string projectName = "HelloWorld"; @@ -287,7 +293,16 @@ public void ILLink_respects_IsTrimmable_attribute(string targetFramework) // Only unused non-trimmable assemblies are kept File.Exists(unusedTrimmableDll).Should().BeFalse(); - DoesImageHaveMethod(unusedNonTrimmableDll, "UnusedMethod").Should().BeTrue(); + if (targetFramework == "net6.0") + { + // In net6.0 the default is to keep assemblies not marked trimmable + DoesImageHaveMethod(unusedNonTrimmableDll, "UnusedMethod").Should().BeTrue(); + } + else + { + // In net7.0+ the default is to keep assemblies not marked trimmable + File.Exists(unusedNonTrimmableDll).Should().BeFalse(); + } } [RequiresMSBuildVersionTheory("17.0.0.32901")] @@ -298,6 +313,7 @@ public void ILLink_IsTrimmable_metadata_can_override_attribute(string targetFram var rid = EnvironmentInfo.GetCompatibleRid(targetFramework); var testProject = CreateTestProjectWithIsTrimmableAttributes(targetFramework, projectName); var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: targetFramework) + .WithProjectChanges(project => SetGlobalTrimMode(project, "partial")) .WithProjectChanges(project => SetMetadata(project, "UnusedTrimmableAssembly", "IsTrimmable", "false")) .WithProjectChanges(project => SetMetadata(project, "UnusedNonTrimmableAssembly", "IsTrimmable", "true")); @@ -316,7 +332,7 @@ public void ILLink_IsTrimmable_metadata_can_override_attribute(string targetFram } [RequiresMSBuildVersionTheory("17.0.0.32901")] - [MemberData(nameof(Net6Plus), MemberType = typeof(PublishTestUtils))] + [InlineData("net6.0")] public void ILLink_TrimMode_applies_to_IsTrimmable_assemblies(string targetFramework) { string projectName = "HelloWorld"; @@ -343,6 +359,47 @@ public void ILLink_TrimMode_applies_to_IsTrimmable_assemblies(string targetFrame DoesImageHaveMethod(unusedNonTrimmableDll, "UnusedMethod").Should().BeTrue(); } + [RequiresMSBuildVersionTheory("17.0.0.32901")] + [InlineData(ToolsetInfo.CurrentTargetFramework, "full")] + [InlineData(ToolsetInfo.CurrentTargetFramework, "partial")] + public void ILLink_TrimMode_new_options(string targetFramework, string trimMode) + { + string projectName = "HelloWorld"; + var rid = EnvironmentInfo.GetCompatibleRid(targetFramework); + var testProject = CreateTestProjectWithIsTrimmableAttributes(targetFramework, projectName); + var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: targetFramework + trimMode) + .WithProjectChanges(project => SetGlobalTrimMode(project, trimMode)); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute($"/p:RuntimeIdentifier={rid}", "-bl").Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName; + + var trimmableDll = Path.Combine(publishDirectory, "TrimmableAssembly.dll"); + var nonTrimmableDll = Path.Combine(publishDirectory, "NonTrimmableAssembly.dll"); + var unusedTrimmableDll = Path.Combine(publishDirectory, "UnusedTrimmableAssembly.dll"); + var unusedNonTrimmableDll = Path.Combine(publishDirectory, "UnusedNonTrimmableAssembly.dll"); + + // Trimmable assemblies are trimmed at member level + DoesImageHaveMethod(trimmableDll, "UnusedMethod").Should().BeFalse(); + DoesImageHaveMethod(trimmableDll, "UsedMethod").Should().BeTrue(); + File.Exists(unusedTrimmableDll).Should().BeFalse(); + if (trimMode is "full") + { + DoesImageHaveMethod(nonTrimmableDll, "UnusedMethod").Should().BeFalse(); + File.Exists(unusedNonTrimmableDll).Should().BeFalse(); + } + else if (trimMode is "partial") + { + DoesImageHaveMethod(nonTrimmableDll, "UnusedMethod").Should().BeTrue(); + DoesImageHaveMethod(unusedNonTrimmableDll, "UnusedMethod").Should().BeTrue(); + } + else + { + Assert.True(false, "unexpected value"); + } + } + [RequiresMSBuildVersionTheory("17.0.0.32901")] [InlineData(ToolsetInfo.CurrentTargetFramework)] public void ILLink_can_set_TrimmerDefaultAction(string targetFramework) @@ -868,8 +925,10 @@ public void ILLink_runs_incrementally(string targetFramework) } [RequiresMSBuildVersionTheory("17.0.0.32901")] - [MemberData(nameof(SupportedTfms), MemberType = typeof(PublishTestUtils))] - public void ILLink_defaults_keep_nonframework(string targetFramework) + [InlineData("netcoreapp3.1")] + [InlineData("net5.0")] + [InlineData("net6.0")] + public void ILLink_old_defaults_keep_nonframework(string targetFramework) { var projectName = "HelloWorld"; var referenceProjectName = "ClassLibForILLink"; @@ -903,6 +962,42 @@ public void ILLink_defaults_keep_nonframework(string targetFramework) DoesDepsFileHaveAssembly(depsFile, unusedFrameworkAssembly).Should().BeFalse(); } + [RequiresMSBuildVersionFact("17.0.0.32901")] + public void ILLink_net7_defaults_trim_nonframework() + { + string targetFramework = "net7.0"; + var projectName = "HelloWorld"; + var referenceProjectName = "ClassLibForILLink"; + var rid = EnvironmentInfo.GetCompatibleRid(targetFramework); + + var testProject = CreateTestProjectForILLinkTesting(targetFramework, projectName, referenceProjectName); + var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: targetFramework); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute("/v:n", $"/p:RuntimeIdentifier={rid}", "/p:PublishTrimmed=true").Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName; + var intermediateDirectory = publishCommand.GetIntermediateDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName; + var linkedDirectory = Path.Combine(intermediateDirectory, "linked"); + + Directory.Exists(linkedDirectory).Should().BeTrue(); + + var linkedDll = Path.Combine(linkedDirectory, $"{projectName}.dll"); + var publishedDll = Path.Combine(publishDirectory, $"{projectName}.dll"); + var unusedDll = Path.Combine(publishDirectory, $"{referenceProjectName}.dll"); + var unusedFrameworkDll = Path.Combine(publishDirectory, $"{unusedFrameworkAssembly}.dll"); + + File.Exists(linkedDll).Should().BeTrue(); + File.Exists(publishedDll).Should().BeTrue(); + File.Exists(unusedDll).Should().BeFalse(); + File.Exists(unusedFrameworkDll).Should().BeFalse(); + + var depsFile = Path.Combine(publishDirectory, $"{projectName}.deps.json"); + DoesDepsFileHaveAssembly(depsFile, projectName).Should().BeTrue(); + DoesDepsFileHaveAssembly(depsFile, referenceProjectName).Should().BeFalse(); + DoesDepsFileHaveAssembly(depsFile, unusedFrameworkAssembly).Should().BeFalse(); + } + [RequiresMSBuildVersionTheory("17.0.0.32901")] [MemberData(nameof(SupportedTfms), MemberType = typeof(PublishTestUtils))] public void ILLink_does_not_include_leftover_artifacts_on_second_run(string targetFramework) diff --git a/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets b/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets index b0fab989cdd4..beab531cc3f0 100644 --- a/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets +++ b/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets @@ -85,6 +85,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. obj/Docker/publish/ EFSQLScripts + + + partial