diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 04b6854fcf73..2c2661ab1329 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -102,13 +102,13 @@
https://github.com/microsoft/vstest
8ee2efd12c3781c5cf850f113f2252fda6a3312b
-
+
https://github.com/mono/linker
- 6b3a3050c70577bd1b3fd7611eef56679e22a4f1
+ 44907d98e524f65db0a0edc2cab8afe077ba812a
-
+
https://github.com/mono/linker
- 6b3a3050c70577bd1b3fd7611eef56679e22a4f1
+ 44907d98e524f65db0a0edc2cab8afe077ba812a
https://github.com/dotnet/runtime
diff --git a/eng/Versions.props b/eng/Versions.props
index 3a0dcbed42a3..20ffc790cd91 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -73,7 +73,7 @@
- 6.0.100-preview.2.21124.3
+ 6.0.100-preview.2.21125.1
$(MicrosoftNETILLinkTasksPackageVersion)
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets
index 859b9bf84620..b01902233296 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets
@@ -118,6 +118,7 @@ Copyright (c) .NET Foundation. All rights reserved.
ReferenceAssemblyPaths="@(ReferencePath)"
RootAssemblyNames="@(TrimmerRootAssembly)"
TrimMode="$(TrimMode)"
+ DefaultAction="$(_TrimmerDefaultAction)"
RemoveSymbols="$(TrimmerRemoveSymbols)"
FeatureSettings="@(_TrimmerFeatureSettings)"
CustomData="@(_TrimmerCustomData)"
@@ -171,14 +172,20 @@ Copyright (c) .NET Foundation. All rights reserved.
in some cases. -->
-
-
+ 5
0
+
+
+
$(TreatWarningsAsErrors)
<_ExtraTrimmerArgs>--skip-unresolved true $(_ExtraTrimmerArgs)
copyused
+
+ <_TrimmerDefaultAction Condition=" $([MSBuild]::VersionLessThan('$(TargetFrameworkVersion)', '6.0')) ">$(TrimMode)
+
+ <_TrimmerDefaultAction Condition=" '$(_TrimmerDefaultAction)' == '' ">copy
-
true
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
copy
+
+
+
+ $(TrimMode)
+
+
+ $(_TrimmerDefaultAction)
+
+
+
<_TrimmerFeatureSettings Include="@(RuntimeHostConfigurationOption)" Condition="'%(RuntimeHostConfigurationOption.Trim)' == 'true'" />
diff --git a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs
index 1c7b06e3a20d..25c67bc7b44d 100644
--- a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs
+++ b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs
@@ -68,6 +68,8 @@ public void ILLink_only_runs_when_switch_is_enabled(string targetFramework)
[Theory]
[InlineData("netcoreapp3.0", true)]
[InlineData("netcoreapp3.0", false)]
+ [InlineData("net5.0", false)]
+ [InlineData("net6.0", false)]
public void ILLink_runs_and_creates_linked_app(string targetFramework, bool referenceClassLibAsPackage)
{
var projectName = "HelloWorld";
@@ -157,6 +159,7 @@ public void PrepareForILLink_can_set_IsTrimmable(string targetFramework)
[RequiresMSBuildVersionTheory("16.8.0")]
[InlineData("net5.0")]
+ [InlineData("net6.0")]
public void PrepareForILLink_can_set_TrimMode(string targetFramework)
{
var projectName = "HelloWorld";
@@ -182,6 +185,7 @@ public void PrepareForILLink_can_set_TrimMode(string targetFramework)
[RequiresMSBuildVersionTheory("16.8.0")]
[InlineData("net5.0")]
+ [InlineData("net6.0")]
public void ILLink_respects_global_TrimMode(string targetFramework)
{
var projectName = "HelloWorld";
@@ -209,6 +213,160 @@ public void ILLink_respects_global_TrimMode(string targetFramework)
DoesImageHaveMethod(isTrimmableDll, "UnusedMethod").Should().BeFalse();
}
+ [RequiresMSBuildVersionTheory("16.8.0")]
+ [InlineData("net5.0")]
+ [InlineData("net6.0")]
+ public void ILLink_roots_IntermediateAssembly(string targetFramework)
+ {
+ var projectName = "HelloWorld";
+ var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);
+
+ var testProject = CreateTestProjectForILLinkTesting(targetFramework, projectName);
+ var testAsset = _testAssetsManager.CreateTestProject(testProject)
+ .WithProjectChanges(project => SetGlobalTrimMode(project, "link"))
+ .WithProjectChanges(project => SetIsTrimmable(project, projectName));
+
+ var publishCommand = new PublishCommand(testAsset);
+ publishCommand.Execute($"/p:RuntimeIdentifier={rid}", $"/p:SelfContained=true", "/p:PublishTrimmed=true").Should().Pass();
+
+ var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName;
+
+ var publishedDll = Path.Combine(publishDirectory, $"{projectName}.dll");
+
+ // The assembly is trimmed but its entry point is kept
+ DoesImageHaveMethod(publishedDll, "UnusedMethod").Should().BeFalse();
+ DoesImageHaveMethod(publishedDll, "Main").Should().BeTrue();
+ }
+
+ [RequiresMSBuildVersionTheory("16.8.0")]
+ [InlineData("net6.0")]
+ public void ILLink_respects_TrimmableAssembly(string targetFramework)
+ {
+ var projectName = "HelloWorld";
+ var referenceProjectName = "ClassLibForILLink";
+ var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);
+
+ var testProject = CreateTestProjectForILLinkTesting(targetFramework, projectName, referenceProjectName);
+ testProject.AdditionalItems["TrimmableAssembly"] = new Dictionary { ["Include"] = referenceProjectName };
+ var testAsset = _testAssetsManager.CreateTestProject(testProject);
+
+ var publishCommand = new PublishCommand(testAsset);
+ publishCommand.Execute($"/p:RuntimeIdentifier={rid}", $"/p:SelfContained=true", "/p:PublishTrimmed=true").Should().Pass();
+
+ var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName;
+
+ var publishedDll = Path.Combine(publishDirectory, $"{projectName}.dll");
+ var unusedTrimmableDll = Path.Combine(publishDirectory, $"{referenceProjectName}.dll");
+
+ File.Exists(publishedDll).Should().BeTrue();
+ // Check that the unused assembly was removed.
+ File.Exists(unusedTrimmableDll).Should().BeFalse();
+ }
+
+ [RequiresMSBuildVersionTheory("16.8.0")]
+ [InlineData("net6.0")]
+ public void ILLink_respects_IsTrimmable_attribute(string targetFramework)
+ {
+ string projectName = "HelloWorld";
+ var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);
+ var testProject = CreateTestProjectWithIsTrimmableAttributes(targetFramework, projectName);
+ var testAsset = _testAssetsManager.CreateTestProject(testProject);
+
+ var publishCommand = new PublishCommand(testAsset);
+ publishCommand.Execute($"/p:RuntimeIdentifier={rid}").Should().Pass();
+
+ var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName;
+
+ var unusedTrimmableDll = Path.Combine(publishDirectory, "UnusedTrimmableAssembly.dll");
+ var unusedNonTrimmableDll = Path.Combine(publishDirectory, "UnusedNonTrimmableAssembly.dll");
+
+ // Only unused non-trimmable assemblies are kept
+ File.Exists(unusedTrimmableDll).Should().BeFalse();
+ DoesImageHaveMethod(unusedNonTrimmableDll, "UnusedMethod").Should().BeTrue();
+ }
+
+ [RequiresMSBuildVersionTheory("16.8.0")]
+ [InlineData("net6.0")]
+ public void ILLink_IsTrimmable_metadata_can_override_attribute(string targetFramework)
+ {
+ string projectName = "HelloWorld";
+ var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);
+ var testProject = CreateTestProjectWithIsTrimmableAttributes(targetFramework, projectName);
+ var testAsset = _testAssetsManager.CreateTestProject(testProject)
+ .WithProjectChanges(project => SetIsTrimmable(project, "UnusedTrimmableAssembly", false))
+ .WithProjectChanges(project => SetIsTrimmable(project, "UnusedNonTrimmableAssembly", true));
+
+ var publishCommand = new PublishCommand(testAsset);
+ publishCommand.Execute($"/p:RuntimeIdentifier={rid}", "/v:n").Should().Pass();
+
+ var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName;
+
+ var unusedTrimmableDll = Path.Combine(publishDirectory, "UnusedTrimmableAssembly.dll");
+ var unusedNonTrimmableDll = Path.Combine(publishDirectory, "UnusedNonTrimmableAssembly.dll");
+
+ // Attributed IsTrimmable assembly with IsTrimmable=false metadata should be kept
+ DoesImageHaveMethod(unusedTrimmableDll, "UnusedMethod").Should().BeTrue();
+ // Unattributed assembly with IsTrimmable=true should be trimmed
+ File.Exists(unusedNonTrimmableDll).Should().BeFalse();
+ }
+
+ [RequiresMSBuildVersionTheory("16.8.0")]
+ [InlineData("net6.0")]
+ public void ILLink_TrimMode_applies_to_IsTrimmable_assemblies(string targetFramework)
+ {
+ string projectName = "HelloWorld";
+ var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);
+ var testProject = CreateTestProjectWithIsTrimmableAttributes(targetFramework, projectName);
+ var testAsset = _testAssetsManager.CreateTestProject(testProject)
+ .WithProjectChanges(project => SetGlobalTrimMode(project, "link"));
+
+ var publishCommand = new PublishCommand(testAsset);
+ publishCommand.Execute($"/p:RuntimeIdentifier={rid}").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();
+ // Non-trimmable assemblies still get copied
+ DoesImageHaveMethod(nonTrimmableDll, "UnusedMethod").Should().BeTrue();
+ DoesImageHaveMethod(unusedNonTrimmableDll, "UnusedMethod").Should().BeTrue();
+ }
+
+ [RequiresMSBuildVersionTheory("16.8.0")]
+ [InlineData("net6.0")]
+ public void ILLink_can_set_TrimmerDefaultAction(string targetFramework)
+ {
+ string projectName = "HelloWorld";
+ var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);
+ var testProject = CreateTestProjectWithIsTrimmableAttributes(targetFramework, projectName);
+ var testAsset = _testAssetsManager.CreateTestProject(testProject)
+ .WithProjectChanges(project => SetTrimmerDefaultAction(project, "link"));
+
+ var publishCommand = new PublishCommand(testAsset);
+ publishCommand.Execute($"/p:RuntimeIdentifier={rid}").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 assembly level
+ DoesImageHaveMethod(trimmableDll, "UnusedMethod").Should().BeTrue();
+ File.Exists(unusedTrimmableDll).Should().BeFalse();
+ // Unattributed assemblies are trimmed at member level
+ DoesImageHaveMethod(nonTrimmableDll, "UnusedMethod").Should().BeFalse();
+ File.Exists(unusedNonTrimmableDll).Should().BeFalse();
+ }
+
[RequiresMSBuildVersionTheory("16.8.0")]
[InlineData("net5.0")]
public void ILLink_analysis_warnings_are_disabled_by_default(string targetFramework)
@@ -445,7 +603,7 @@ public void ILLink_accepts_root_descriptor(string targetFramework)
// keeps all used assemblies, but in this case we want to
// check whether the root descriptor actually roots only
// the specified method.
- var extraArgs = $"-p link {referenceProjectName}";
+ var extraArgs = $"--action link {referenceProjectName}";
publishCommand.Execute($"/p:RuntimeIdentifier={rid}", $"/p:SelfContained=true", "/p:PublishTrimmed=true",
$"/p:_ExtraTrimmerArgs={extraArgs}", "/v:n").Should().Pass();
@@ -502,7 +660,7 @@ public void ILLink_respects_feature_settings_from_host_config()
var publishCommand = new PublishCommand(testAsset);
publishCommand.Execute($"/p:RuntimeIdentifier={rid}", $"/p:SelfContained=true", "/p:PublishTrimmed=true",
- $"/p:_ExtraTrimmerArgs=-p link {referenceProjectName}").Should().Pass();
+ $"/p:_ExtraTrimmerArgs=--action link {referenceProjectName}").Should().Pass();
var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName;
var referenceDll = Path.Combine(publishDirectory, $"{referenceProjectName}.dll");
@@ -535,7 +693,7 @@ public void ILLink_ignores_host_config_settings_with_link_false()
var publishCommand = new PublishCommand(testAsset);
publishCommand.Execute($"/p:RuntimeIdentifier={rid}", $"/p:SelfContained=true", "/p:PublishTrimmed=true",
- $"/p:_ExtraTrimmerArgs=-p link {referenceProjectName}").Should().Pass();
+ $"/p:_ExtraTrimmerArgs=--action link {referenceProjectName}").Should().Pass();
var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework, runtimeIdentifier: rid).FullName;
var referenceDll = Path.Combine(publishDirectory, $"{referenceProjectName}.dll");
@@ -1055,18 +1213,25 @@ public void It_warns_when_targetting_netcoreapp_2_x_illink()
.HaveStdOutContaining(Strings.PublishTrimmedRequiresVersion30);
}
- private void SetIsTrimmable(XDocument project, string assemblyName)
+ private void SetIsTrimmable(XDocument project, string assemblyName, bool value = true)
{
var ns = project.Root.Name.Namespace;
- var target = new XElement(ns + "Target",
+ var target = project.Root.Elements(ns + "Target")
+ .Where(e => e.Attribute("Name")?.Value == "SetIsTrimmable")
+ .FirstOrDefault();
+
+ if (target == null) {
+ target = new XElement(ns + "Target",
new XAttribute("BeforeTargets", "PrepareForILLink"),
new XAttribute("Name", "SetIsTrimmable"));
- project.Root.Add(target);
+ project.Root.Add(target);
+ }
+
target.Add(new XElement(ns + "ItemGroup",
new XElement("ManagedAssemblyToLink",
new XAttribute("Condition", $"'%(FileName)' == '{assemblyName}'"),
- new XElement("IsTrimmable", "true"))));
+ new XElement("IsTrimmable", value.ToString()))));
}
private void SetTrimMode(XDocument project, string assemblyName, string trimMode)
@@ -1093,6 +1258,15 @@ private void SetGlobalTrimMode(XDocument project, string trimMode)
trimMode));
}
+ private void SetTrimmerDefaultAction(XDocument project, string action)
+ {
+ var ns = project.Root.Name.Namespace;
+
+ var properties = new XElement(ns + "PropertyGroup");
+ project.Root.Add(properties);
+ properties.Add(new XElement(ns + "_TrimmerDefaultAction", action));
+ }
+
private void EnableNonFrameworkTrimming(XDocument project)
{
// Used to override the default linker options for testing
@@ -1211,6 +1385,110 @@ public override void IL_2046() {}
return testProject;
}
+ private TestProject CreateTestProjectWithIsTrimmableAttributes (
+ string targetFramework,
+ string projectName)
+ {
+ var testProject = new TestProject()
+ {
+ Name = projectName,
+ TargetFrameworks = targetFramework,
+ IsExe = true
+ };
+ testProject.AdditionalProperties["PublishTrimmed"] = "true";
+
+ testProject.SourceFiles[$"{projectName}.cs"] = @"
+using System;
+public class Program
+{
+ public static void Main()
+ {
+ TrimmableAssembly.UsedMethod();
+ NonTrimmableAssembly.UsedMethod();
+ }
+}";
+
+ var trimmableProject = new TestProject()
+ {
+ Name = "TrimmableAssembly",
+ TargetFrameworks = targetFramework
+ };
+
+ trimmableProject.SourceFiles["TrimmableAssembly.cs"] = @"
+using System.Reflection;
+
+[assembly: AssemblyMetadata(""IsTrimmable"", ""True"")]
+
+public static class TrimmableAssembly
+{
+ public static void UsedMethod()
+ {
+ }
+
+ public static void UnusedMethod()
+ {
+ }
+}";
+ testProject.ReferencedProjects.Add (trimmableProject);
+
+ var nonTrimmableProject = new TestProject()
+ {
+ Name = "NonTrimmableAssembly",
+ TargetFrameworks = targetFramework
+ };
+
+ nonTrimmableProject.SourceFiles["NonTrimmableAssembly.cs"] = @"
+public static class NonTrimmableAssembly
+{
+ public static void UsedMethod()
+ {
+ }
+
+ public static void UnusedMethod()
+ {
+ }
+}";
+ testProject.ReferencedProjects.Add (nonTrimmableProject);
+
+ var unusedTrimmableProject = new TestProject()
+ {
+ Name = "UnusedTrimmableAssembly",
+ TargetFrameworks = targetFramework
+ };
+
+ unusedTrimmableProject.SourceFiles["UnusedTrimmableAssembly.cs"] = @"
+using System.Reflection;
+
+[assembly: AssemblyMetadata(""IsTrimmable"", ""True"")]
+
+public static class UnusedTrimmableAssembly
+{
+ public static void UnusedMethod()
+ {
+ }
+}
+";
+ testProject.ReferencedProjects.Add (unusedTrimmableProject);
+
+ var unusedNonTrimmableProject = new TestProject()
+ {
+ Name = "UnusedNonTrimmableAssembly",
+ TargetFrameworks = targetFramework
+ };
+
+ unusedNonTrimmableProject.SourceFiles["UnusedNonTrimmableAssembly.cs"] = @"
+public static class UnusedNonTrimmableAssembly
+{
+ public static void UnusedMethod()
+ {
+ }
+}
+";
+ testProject.ReferencedProjects.Add (unusedNonTrimmableProject);
+
+ return testProject;
+ }
+
private TestProject CreateTestProjectForILLinkTesting(
string targetFramework,
string mainProjectName,
@@ -1236,6 +1514,10 @@ public static void Main()
{
Console.WriteLine(""Hello world"");
}
+
+ public static void UnusedMethod()
+ {
+ }
";
if (addAssemblyReference)