Skip to content

[meta] Replace the custom linker steps with different solutions #17693

@rolfbjarne

Description

@rolfbjarne

Note: this issue is incomplete, it still lacks a lot of detail.

We're using custom linker steps during our build in order to easily reuse pre-existing code we have for legacy Xamarin.iOS/Xamarin.Mac builds (mtouch/mmp).

  • The ILLinker team wants to remove support for custom linker steps, since it simplifies their code base and makes it easier for them to innovate.

  • The NativeAOT compiler trims code, but doesn't support custom linker steps. If we want to support NativeAOT without having to also run ILLinker first, we'll have to replace the custom linker steps with a different solution.

  • Removing custom linker steps would make it possible to run the linker from Windows (instead of remotely on Mac), which would speed up and simplify the Windows build (additionally we wouldn't need to have our own override/implementation for the linker MSBuild logic - our _RunILLink target could be removed:

    <!-- Overrides Core SDK target to remote the ILLink execution -->
    <!-- https://github.com/dotnet/sdk/blob/cdf57465e1cba9e44b5c9a76a403d41b1b8f178c/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets#L76-L132 -->
    <Target Name="_RunILLink"
    DependsOnTargets="_ComputeManagedAssemblyToLink;PrepareForILLink;_FindILLink"
    Inputs="$(MSBuildAllProjects);@(ManagedAssemblyToLink);@(TrimmerRootDescriptor);@(ReferencePath)"
    Outputs="$(_LinkSemaphore)">
    <PropertyGroup>
    <!-- The .NET 7 linker sets _TrimmerDefaultAction instead of TrimmerDefaultAction, so copy that value if it's set (and TrimmerDefaultAction is not set) -->
    <TrimmerDefaultAction Condition="'$(TrimmerDefaultAction)' == ''">$(_TrimmerDefaultAction)</TrimmerDefaultAction>
    <_RemoteExtraTrimmerArgs Condition="'$(_ExtraTrimmerArgs)' != ''">$(_ExtraTrimmerArgs.Replace('$(NetCoreRoot)', '$(_DotNetRootRemoteDirectory)'))</_RemoteExtraTrimmerArgs>
    </PropertyGroup>
    <!-- Include Debug symbols as input so those are copied to the server -->
    <ItemGroup>
    <!-- @(_PDBToLink) comes from the _ComputeManagedAssemblyToLink target, which is a dependency of this target and is included in Microsoft.NET.ILLink.targets -->
    <!-- @(_PDBToLink) should contain the current assembly pdb and the project reference pdbs -->
    <_ILLinkDebugSymbols Include="@(_PDBToLink)" />
    </ItemGroup>
    <Delete SessionId="$(BuildSessionId)" Files="@(_LinkedResolvedFileToPublishCandidate)" />
    <Xamarin.MacDev.Tasks.ILLink
    SessionId="$(BuildSessionId)"
    AssemblyPaths="@(ManagedAssemblyToLink)"
    ReferenceAssemblyPaths="@(ReferencePath)"
    RootAssemblyNames="@(TrimmerRootAssembly)"
    TrimMode="$(TrimMode)"
    DefaultAction="$(_TrimmerDefaultAction)"
    RemoveSymbols="$(TrimmerRemoveSymbols)"
    FeatureSettings="@(_TrimmerFeatureSettings)"
    CustomData="@(_TrimmerCustomData)"
    BeforeFieldInit="$(_TrimmerBeforeFieldInit)"
    OverrideRemoval="$(_TrimmerOverrideRemoval)"
    UnreachableBodies="$(_TrimmerUnreachableBodies)"
    UnusedInterfaces="$(_TrimmerUnusedInterfaces)"
    IPConstProp="$(_TrimmerIPConstProp)"
    Sealer="$(_TrimmerSealer)"
    Warn="$(ILLinkWarningLevel)"
    NoWarn="$(NoWarn)"
    TreatWarningsAsErrors="$(ILLinkTreatWarningsAsErrors)"
    WarningsAsErrors="$(WarningsAsErrors)"
    WarningsNotAsErrors="$(WarningsNotAsErrors)"
    SingleWarn="$(TrimmerSingleWarn)"
    CustomSteps="@(_TrimmerCustomSteps)"
    RootDescriptorFiles="@(TrimmerRootDescriptor)"
    OutputDirectory="$(IntermediateLinkDir)"
    DumpDependencies="$(_TrimmerDumpDependencies)"
    DependenciesFileFormat="$(_TrimmerDependenciesFileFormat)"
    ExtraArgs="$(_RemoteExtraTrimmerArgs)"
    ToolExe="$(_DotNetHostFileName)"
    ToolPath="$(_DotNetHostDirectory)"
    ILLinkPath="$(_RemoteILLinkPath)"
    LinkerItemsDirectory="$(_LinkerItemsDirectory)"
    LinkerCacheDirectory="$(_LinkerCacheDirectory)"
    DebugSymbols="@(_ILLinkDebugSymbols)"
    ContinueOnError="ErrorAndContinue">
    <Output TaskParameter="ExitCode" PropertyName="_ILLinkExitCode" />
    </Xamarin.MacDev.Tasks.ILLink>
    <PropertyGroup>
    <!-- Sometimes the ILLink task itself fails, in which case the _ILLinkExitCode value won't be set. In this case, set it to a value that will cause the expected error to be shown later on. -->
    <_ILLinkExitCode Condition="'$(_ILLinkExitCode)' == '' And '$(MSBuildLastTaskResult)' != 'true'">-1</_ILLinkExitCode>
    </PropertyGroup>
    <Touch Files="$(_LinkSemaphore)" AlwaysCreate="true" Condition=" '$(_ILLinkExitCode)' == '0' " />
    </Target>
    , as well as our ILLink implementation: https://github.com/dotnet/macios/blob/cd400a2c90ac1a3cbe6602304813295a976b4a0a/msbuild/Xamarin.MacDev.Tasks/Tasks/ILLink.cs)

  • Hopefully we'll be able to speed up Debug builds, by not having to run the linker, and instead a slimmed down (thus faster) version of just what we need.

The custom linker steps are used for multiple purposes, and multiple different solutions will have to be implemented. The main idea is to add msbuild tasks that are executed before and after the trimmer (either ILLinker or NativeAOT).

  • We need to track of build times. A big advantage of the custom linker steps is that assemblies are only loaded once, during trimming. Adding other msbuild tasks that are executed before and after trimming means we'll have to load assemblies more than once, and this might slow down the build. We'll have monitor this and get numbers how bad it is for each step of the way.

(add list of all the custom linker steps)
(separate the removal of each step into it's own issue)

For app extensions, we'll need to generate an XML with everything we want rooted (ref: #19037). This way we'll hopefully be able to avoid this workaround: #19049

Some people have suggested using System.Reflection.Metadata, but there's one problem with this: there's no good API to modify assemblies (dotnet/runtime#30443).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementThe issue or pull request is an enhancement

    Type

    No fields configured for Epic.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions