Skip to content

Commit

Permalink
Merge pull request #26246 from agocke/trimmode
Browse files Browse the repository at this point in the history
Change linker to trim everything by default in 7.0
  • Loading branch information
agocke authored Jul 9, 2022
2 parents 7e17472 + 8c7835d commit 83a2f2c
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 34 deletions.
8 changes: 4 additions & 4 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,19 @@
<Uri>https://github.com/microsoft/vstest</Uri>
<Sha>b1452a516a80672fa814f4ebdf2151c0875d0b20</Sha>
</Dependency>
<Dependency Name="Microsoft.NET.ILLink.Tasks" Version="7.0.100-1.22354.1">
<Dependency Name="Microsoft.NET.ILLink.Tasks" Version="7.0.100-1.22357.2">
<Uri>https://github.com/dotnet/linker</Uri>
<Sha>ef2d0f25b72469b55925251a79f12bcbf98644bf</Sha>
<Sha>b6f1fb4d67f6aab530382e1973b898ba858709e0</Sha>
<SourceBuild RepoName="linker" ManagedOnly="true" />
</Dependency>
<Dependency Name="Microsoft.DotNet.ILCompiler" Version="7.0.0-preview.6.22356.1">
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>206dccb7945aaa3f26599fbe742de9022ca7ef91</Sha>
<SourceBuildTarball RepoName="runtime" ManagedOnly="true" />
</Dependency>
<Dependency Name="Microsoft.NET.ILLink.Analyzers" Version="7.0.100-1.22354.1">
<Dependency Name="Microsoft.NET.ILLink.Analyzers" Version="7.0.100-1.22357.2">
<Uri>https://github.com/dotnet/linker</Uri>
<Sha>ef2d0f25b72469b55925251a79f12bcbf98644bf</Sha>
<Sha>b6f1fb4d67f6aab530382e1973b898ba858709e0</Sha>
</Dependency>
<Dependency Name="System.CodeDom" Version="7.0.0-preview.6.22356.1">
<Uri>https://github.com/dotnet/runtime</Uri>
Expand Down
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
</PropertyGroup>
<PropertyGroup>
<!-- Dependencies from https://github.com/mono/linker -->
<MicrosoftNETILLinkTasksPackageVersion>7.0.100-1.22354.1</MicrosoftNETILLinkTasksPackageVersion>
<MicrosoftNETILLinkTasksPackageVersion>7.0.100-1.22357.2</MicrosoftNETILLinkTasksPackageVersion>
<MicrosoftNETILLinkAnalyzerPackageVersion>$(MicrosoftNETILLinkTasksPackageVersion)</MicrosoftNETILLinkAnalyzerPackageVersion>
</PropertyGroup>
<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Copyright (c) .NET Foundation. All rights reserved.

<!-- Trimmer defaults -->
<PublishTrimmed Condition="'$(PublishTrimmed)' == ''">true</PublishTrimmed>
<TrimMode Condition="'$(TrimMode)' == ''">link</TrimMode>
<TrimMode Condition="'$(TrimMode)' == ''">partial</TrimMode>
<TrimmerRemoveSymbols Condition="'$(TrimmerRemoveSymbols)' == ''">false</TrimmerRemoveSymbols>

<!-- Static web assets defaults -->
Expand Down
82 changes: 60 additions & 22 deletions src/Tests/Microsoft.NET.Build.Tests/ReferenceExeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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)
{
Expand All @@ -62,16 +87,22 @@ private void CreateProjects()
IsExe = true,
};

ReferencedProject.AdditionalProperties["UseAppHost"] = "true";

if (ReferencedSelfContained)
{
ReferencedProject.RuntimeIdentifier = EnvironmentInfo.GetCompatibleRid();
}

// 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);
}
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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();
}

Expand Down
107 changes: 101 additions & 6 deletions src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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";
Expand All @@ -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 {
Expand Down Expand Up @@ -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";
Expand All @@ -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")]
Expand All @@ -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"));

Expand All @@ -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";
Expand All @@ -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)
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<PublishIntermediateOutputPath Condition="'$(DockerPublish)' == 'true'">obj/Docker/publish/</PublishIntermediateOutputPath>

<EFSQLScriptsFolderName Condition="$(EFSQLScriptsFolderName) == ''">EFSQLScripts</EFSQLScriptsFolderName>

<!-- If TrimMode has not already been set, default to partial trimming, as AspNet is not currently fully trim-compatible -->
<TrimMode Condition="'$(PublishTrimmed)' == 'true' and '$(TrimMode)' == ''">partial</TrimMode>
</PropertyGroup>

<!--
Expand Down

0 comments on commit 83a2f2c

Please sign in to comment.