Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ Copyright (c) .NET Foundation. All rights reserved.

<Import Project="Sdk.props" Sdk="Microsoft.NET.ILLink.Tasks" />

<PropertyGroup Condition=" '$(PublishTrimmed)' == 'true' ">
<!-- These properties should be set even if PublishTrimmed != true, to allow SDK components to
set PublishTrimmed in targets which are imported after these targets. -->
<PropertyGroup>
<IntermediateLinkDir Condition=" '$(IntermediateLinkDir)' == '' ">$(IntermediateOutputPath)linked\</IntermediateLinkDir>
<IntermediateLinkDir Condition=" !HasTrailingSlash('$(IntermediateLinkDir)') ">$(IntermediateLinkDir)\</IntermediateLinkDir>
<!-- Used to enable incremental build for the linker target. -->
Expand All @@ -37,21 +39,21 @@ Copyright (c) .NET Foundation. All rights reserved.
<NETSdkInformation ResourceName="ILLink_Info" />

<ItemGroup>
<_LinkedResolvedFileToPublish Include="@(_LinkedResolvedFileToPublishCandidates)" Condition="Exists('%(Identity)')" />
<ResolvedFileToPublish Remove="@(_ManagedAssembliesToLink)" />
<_LinkedResolvedFileToPublish Include="@(_LinkedResolvedFileToPublishCandidate)" Condition="Exists('%(Identity)')" />
<ResolvedFileToPublish Remove="@(ManagedAssemblyToLink)" />
<ResolvedFileToPublish Include="@(_LinkedResolvedFileToPublish)" />
</ItemGroup>

<!-- Remove assemblies from inputs to GenerateDepsFile. See
https://github.com/dotnet/sdk/pull/3086 -->
<ItemGroup>
<_RemovedManagedAssemblies Include="@(_ManagedAssembliesToLink)" Condition="!Exists('$(IntermediateLinkDir)%(Filename)%(Extension)')" />
<_RemovedManagedAssembly Include="@(ManagedAssemblyToLink)" Condition="!Exists('$(IntermediateLinkDir)%(Filename)%(Extension)')" />

<ResolvedCompileFileDefinitions Remove="@(_RemovedManagedAssemblies)" />
<RuntimeCopyLocalItems Remove="@(_RemovedManagedAssemblies)" />
<RuntimeTargetsCopyLocalItems Remove="@(_RemovedManagedAssemblies)" />
<UserRuntimeAssembly Remove="@(_RemovedManagedAssemblies)" />
<RuntimePackAsset Remove="@(_RemovedManagedAssemblies)" />
<ResolvedCompileFileDefinitions Remove="@(_RemovedManagedAssembly)" />
<RuntimeCopyLocalItems Remove="@(_RemovedManagedAssembly)" />
<RuntimeTargetsCopyLocalItems Remove="@(_RemovedManagedAssembly)" />
<UserRuntimeAssembly Remove="@(_RemovedManagedAssembly)" />
<RuntimePackAsset Remove="@(_RemovedManagedAssembly)" />
</ItemGroup>

</Target>
Expand All @@ -67,8 +69,8 @@ Copyright (c) .NET Foundation. All rights reserved.
-->
<UsingTask TaskName="ILLink" AssemblyFile="$(ILLinkTasksAssembly)" />
<Target Name="_RunILLink"
DependsOnTargets="_ComputeManagedAssembliesToLink;_SetILLinkDefaults"
Inputs="$(MSBuildAllProjects);@(_ManagedAssembliesToLink);@(TrimmerRootDescriptor);@(ReferencePath)"
DependsOnTargets="_ComputeManagedAssemblyToLink;PrepareForILLink"
Inputs="$(MSBuildAllProjects);@(ManagedAssemblyToLink);@(TrimmerRootDescriptor);@(ReferencePath)"
Outputs="$(_LinkSemaphore)">

<!-- When running from Desktop MSBuild, DOTNET_HOST_PATH is not set.
Expand All @@ -79,13 +81,21 @@ Copyright (c) .NET Foundation. All rights reserved.
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
</PropertyGroup>

<Delete Files="@(_LinkedResolvedFileToPublishCandidates)" />
<ILLink AssemblyPaths="@(_ManagedAssembliesToLink)"
<!-- Temporary workaround until the naming is finalized. Translate from TrimMode to Action metadata. -->
<ItemGroup>
<ManagedAssemblyToLink Condition=" '%(ManagedAssemblyToLink.TrimMode)' != '' ">
<Action>%(ManagedAssemblyToLink.TrimMode)</Action>
</ManagedAssemblyToLink>
</ItemGroup>

<Delete Files="@(_LinkedResolvedFileToPublishCandidate)" />
<ILLink AssemblyPaths="@(ManagedAssemblyToLink)"
ReferenceAssemblyPaths="@(ReferencePath)"
RootAssemblyNames="@(TrimmerRootAssembly)"
DefaultAction="$(_TrimmerDefaultAction)"
DefaultAction="$(TrimMode)"
LinkSymbols="$(_TrimmerLinkSymbols)"
FeatureSettings="@(_TrimmerFeatureSettings)"
CustomData="@(_TrimmerCustomData)"

BeforeFieldInit="$(_TrimmerBeforeFieldInit)"
OverrideRemoval="$(_TrimmerOverrideRemoval)"
Expand All @@ -109,12 +119,18 @@ Copyright (c) .NET Foundation. All rights reserved.

<!--
============================================================
_SetILLinkDefaults
PrepareForILLink

Set up the default options and inputs to ILLink.
Set up the default options and inputs to ILLink. Other targets are expected to hook into
this extension point via BeforeTargets/AfterTargets to opt assemblies into or out of trimming
using global ILLink options, or per-assembly IsTrimmable and TrimMode metadata.

Note that adding items to or removing items from ManagedAssemblyToLink is unsupported. To change
the set of inputs to the linker, instead use a different extension point to
set PostprocessAssembly metadata on ResolvedFileToPublish.
-->
<Target Name="_SetILLinkDefaults"
DependsOnTargets="_ComputeManagedAssembliesToLink">
<Target Name="PrepareForILLink"
DependsOnTargets="_ComputeManagedAssemblyToLink">

<!-- The defaults currently root non-framework assemblies, which
is a no-op for portable apps. If we later support more ways
Expand All @@ -124,13 +140,19 @@ Copyright (c) .NET Foundation. All rights reserved.

<PropertyGroup>
<_ExtraTrimmerArgs>--skip-unresolved true $(_ExtraTrimmerArgs)</_ExtraTrimmerArgs>
<_TrimmerDefaultAction Condition=" '$(_TrimmerDefaultAction)' == '' ">copyused</_TrimmerDefaultAction>
<TrimMode Condition=" '$(TrimMode)' == '' ">copyused</TrimMode>
</PropertyGroup>

<ItemGroup>
<TrimmerRootAssembly Include="@(_ManagedAssembliesToLink)" Condition=" '%(_ManagedAssembliesToLink.IsTrimmable)' != 'true' " />
<_ManagedAssembliesToLink Condition=" '%(_ManagedAssembliesToLink.IsTrimmable)' != 'true' ">
<action>copy</action>
</_ManagedAssembliesToLink>
<!-- Treat any assemblies that already have customized TrimMode as trimmable. -->
<ManagedAssemblyToLink Condition=" '%(ManagedAssemblyToLink.TrimMode)' != '' ">
<IsTrimmable>true</IsTrimmable>
</ManagedAssemblyToLink>
<!-- Root and copy non-trimmable assemblies. -->
<TrimmerRootAssembly Include="@(ManagedAssemblyToLink)" Condition=" '%(ManagedAssemblyToLink.IsTrimmable)' != 'true' " />
<ManagedAssemblyToLink Condition=" '%(ManagedAssemblyToLink.IsTrimmable)' != 'true' ">
<TrimMode>copy</TrimMode>
</ManagedAssemblyToLink>
</ItemGroup>

<ItemGroup>
Expand All @@ -141,22 +163,22 @@ Copyright (c) .NET Foundation. All rights reserved.

<!--
============================================================
_ComputeManagedAssembliesToLink
_ComputeManagedAssemblyToLink

Compute the set of inputs to the linker.
============================================================
-->
<UsingTask TaskName="ComputeManagedAssemblies" AssemblyFile="$(ILLinkTasksAssembly)" />
<Target Name="_ComputeManagedAssembliesToLink" DependsOnTargets="_ComputeAssembliesToPostprocessOnPublish">
<Target Name="_ComputeManagedAssemblyToLink" DependsOnTargets="_ComputeAssembliesToPostprocessOnPublish">

<!-- NB: There should not be non-managed assemblies in this list, but we still give the linker a chance to
further refine this list. It currently drops C++/CLI assemblies in ComputeManageAssemblies. -->
<ComputeManagedAssemblies Assemblies="@(ResolvedFileToPublish->WithMetadataValue('PostprocessAssembly', 'true'))">
<Output TaskParameter="ManagedAssemblies" ItemName="_ManagedAssembliesToLink" />
<Output TaskParameter="ManagedAssemblies" ItemName="ManagedAssemblyToLink" />
</ComputeManagedAssemblies>

<ItemGroup>
<_LinkedResolvedFileToPublishCandidates Include="@(_ManagedAssembliesToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" />
<_LinkedResolvedFileToPublishCandidate Include="@(ManagedAssemblyToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" />
</ItemGroup>

</Target>
Expand Down
139 changes: 127 additions & 12 deletions src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToRunILLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,86 @@ public void ILLink_runs_and_creates_linked_app(string targetFramework, bool refe
DoesDepsFileHaveAssembly(depsFile, unusedFrameworkAssembly).Should().BeFalse();
}

[Theory]
[InlineData("net5.0")]
public void PrepareForILLink_can_set_IsTrimmable(string targetFramework)
{
var projectName = "HelloWorld";
var referenceProjectName = "ClassLibForILLink";
var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);

var testProject = CreateTestProjectForILLinkTesting(targetFramework, projectName, referenceProjectName);
var testAsset = _testAssetsManager.CreateTestProject(testProject)
.WithProjectChanges(project => SetIsTrimmable(project, referenceProjectName));

var publishCommand = new PublishCommand(Log, Path.Combine(testAsset.TestRoot, testProject.Name));
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 unusedIsTrimmableDll = Path.Combine(publishDirectory, $"{referenceProjectName}.dll");

File.Exists(publishedDll).Should().BeTrue();
// Check that the unused trimmable assembly was removed
File.Exists(unusedIsTrimmableDll).Should().BeFalse();
}

[Theory]
[InlineData("net5.0")]
public void PrepareForILLink_can_set_TrimMode(string targetFramework)
{
var projectName = "HelloWorld";
var referenceProjectName = "ClassLibForILLink";
var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);

var testProject = CreateTestProjectForILLinkTesting(targetFramework, projectName, referenceProjectName);
var testAsset = _testAssetsManager.CreateTestProject(testProject)
.WithProjectChanges(project => SetTrimMode(project, referenceProjectName, "link"));

var publishCommand = new PublishCommand(Log, Path.Combine(testAsset.TestRoot, testProject.Name));
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 unusedTrimModeLinkDll = Path.Combine(publishDirectory, $"{referenceProjectName}.dll");

File.Exists(publishedDll).Should().BeTrue();
// Check that the unused "link" assembly was removed.
File.Exists(unusedTrimModeLinkDll).Should().BeFalse();
}

[Theory]
[InlineData("net5.0")]
public void ILLink_respects_global_TrimMode(string targetFramework)
{
var projectName = "HelloWorld";
var referenceProjectName = "ClassLibForILLink";
var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);

var testProject = CreateTestProjectForILLinkTesting(targetFramework, projectName, referenceProjectName);
var testAsset = _testAssetsManager.CreateTestProject(testProject)
.WithProjectChanges(project => SetGlobalTrimMode(project, "link"))
.WithProjectChanges(project => SetIsTrimmable(project, referenceProjectName))
.WithProjectChanges(project => AddRootDescriptor(project, $"{referenceProjectName}.xml"));

var publishCommand = new PublishCommand(Log, Path.Combine(testAsset.TestRoot, testProject.Name));
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 isTrimmableDll = Path.Combine(publishDirectory, $"{referenceProjectName}.dll");

File.Exists(publishedDll).Should().BeTrue();
File.Exists(isTrimmableDll).Should().BeTrue();
// Check that the assembly was trimmed at the member level
DoesImageHaveMethod(isTrimmableDll, "UnusedMethodToRoot").Should().BeTrue();
DoesImageHaveMethod(isTrimmableDll, "UnusedMethod").Should().BeFalse();
}


[Theory]
[InlineData("netcoreapp3.0")]
public void ILLink_accepts_root_descriptor(string targetFramework)
Expand Down Expand Up @@ -494,30 +574,65 @@ public void It_warns_when_targetting_netcoreapp_2_x()
.HaveStdOutContaining(Strings.PublishTrimmedRequiresVersion30);
}

private void SetIsTrimmable(XDocument project, string assemblyName)
{
var ns = project.Root.Name.Namespace;

var target = new XElement(ns + "Target",
new XAttribute("BeforeTargets", "PrepareForILLink"),
new XAttribute("Name", "SetIsTrimmable"));
project.Root.Add(target);
target.Add(new XElement(ns + "ItemGroup",
new XElement("ManagedAssemblyToLink",
new XAttribute("Condition", $"'%(FileName)' == '{assemblyName}'"),
new XElement("IsTrimmable", "true"))));
}

private void SetTrimMode(XDocument project, string assemblyName, string trimMode)
{
var ns = project.Root.Name.Namespace;

var target = new XElement(ns + "Target",
new XAttribute("BeforeTargets", "PrepareForILLink"),
new XAttribute("Name", "SetTrimMode"));
project.Root.Add(target);
target.Add(new XElement(ns + "ItemGroup",
new XElement("ManagedAssemblyToLink",
new XAttribute("Condition", $"'%(FileName)' == '{assemblyName}'"),
new XElement("TrimMode", trimMode))));
}

private void SetGlobalTrimMode(XDocument project, string trimMode)
{
var ns = project.Root.Name.Namespace;

var properties = new XElement(ns + "PropertyGroup");
project.Root.Add(properties);
properties.Add(new XElement(ns + "TrimMode",
trimMode));
}

private void EnableNonFrameworkTrimming(XDocument project)
{
// Used to override the default linker options for testing
// purposes. The default roots non-framework assemblies,
// but we want to ensure that the linker is running
// end-to-end by checking that it strips code from our
// test projects.

SetGlobalTrimMode(project, "link");
var ns = project.Root.Name.Namespace;

var target = new XElement(ns + "Target",
new XAttribute("AfterTargets", "_SetILLinkDefaults"),
new XAttribute("BeforeTargets", "PrepareForILLink"),
new XAttribute("Name", "_EnableNonFrameworkTrimming"));
project.Root.Add(target);
target.Add(new XElement(ns + "PropertyGroup",
new XElement("_TrimmerDefaultAction", "link")));
target.Add(new XElement(ns + "ItemGroup",
new XElement("TrimmerRootAssembly",
new XAttribute("Remove", "@(TrimmerRootAssembly)")),
new XElement("TrimmerRootAssembly",
new XAttribute("Include", "@(IntermediateAssembly->'%(FileName)')")),
new XElement("_ManagedAssembliesToLink",
new XAttribute("Update", "@(_ManagedAssembliesToLink)"),
new XElement("action"))));
var items = new XElement(ns + "ItemGroup");
target.Add(items);
items.Add(new XElement("ManagedAssemblyToLink",
new XElement("Condition", "true"),
new XElement("IsTrimmable", "true")));
items.Add(new XElement(ns + "TrimmerRootAssembly",
new XAttribute("Include", "@(IntermediateAssembly->'%(FileName)')")));
}

static readonly string substitutionsFilename = "ILLink.Substitutions.xml";
Expand Down