diff --git a/src/tools/illink/src/ILLink.Tasks/LinkTask.cs b/src/tools/illink/src/ILLink.Tasks/LinkTask.cs
new file mode 100644
index 00000000000..b89b893be68
--- /dev/null
+++ b/src/tools/illink/src/ILLink.Tasks/LinkTask.cs
@@ -0,0 +1,511 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace ILLink.Tasks
+{
+ public class ILLink : ToolTask
+ {
+ ///
+ /// Paths to the assembly files that should be considered as
+ /// input to the linker.
+ /// Optional metadata:
+ /// TrimMode ("copy", "link", etc...): sets the illink action to take for this assembly.
+ /// There is an optional metadata for each optimization that can be set to "True" or "False" to
+ /// enable or disable it per-assembly:
+ /// BeforeFieldInit
+ /// OverrideRemoval
+ /// UnreachableBodies
+ /// UnusedInterfaces
+ /// IPConstProp
+ /// Sealer
+ /// Optional metadata "TrimmerSingleWarn" may also be set to "True"/"False" to control
+ /// whether the linker produces granular warnings for this assembly.
+ /// Maps to '-reference', and possibly '--action', '--enable-opt', '--disable-opt', '--verbose'
+ ///
+ [Required]
+ public ITaskItem[] AssemblyPaths { get; set; }
+
+ ///
+ /// Paths to assembly files that are reference assemblies,
+ /// representing the surface area for compilation.
+ /// Maps to '-reference', with action set to 'skip' via '--action'.
+ ///
+ public ITaskItem[] ReferenceAssemblyPaths { get; set; }
+
+ ///
+ /// The names of the assemblies to root. This should contain
+ /// assembly names without an extension, not file names or
+ /// paths. The default is to root everything in these assemblies.
+ // For more fine-grained control, set RootMode metadata.
+ ///
+ [Required]
+ public ITaskItem[] RootAssemblyNames { get; set; }
+
+ ///
+ /// The directory in which to place linked assemblies.
+ /// Maps to '-out'.
+ ///
+ [Required]
+ public ITaskItem OutputDirectory { get; set; }
+
+ ///
+ /// The subset of warnings that have to be turned off.
+ /// Maps to '--nowarn'.
+ ///
+ public string NoWarn { get; set; }
+
+ ///
+ /// The warning version to use.
+ /// Maps to '--warn'.
+ ///
+ public string Warn { get; set; }
+
+ ///
+ /// Treat all warnings as errors.
+ /// Maps to '--warnaserror' if true, '--warnaserror-' if false.
+ ///
+ public bool TreatWarningsAsErrors { set => _treatWarningsAsErrors = value; }
+ bool? _treatWarningsAsErrors;
+
+ ///
+ /// Produce at most one trim analysis warning per assembly.
+ /// Maps to '--singlewarn' if true, '--singlewarn-' if false.
+ ///
+ public bool SingleWarn { set => _singleWarn = value; }
+ bool? _singleWarn;
+
+ ///
+ /// The list of warnings to report as errors.
+ /// Maps to '--warnaserror LIST-OF-WARNINGS'.
+ ///
+ public string WarningsAsErrors { get; set; }
+
+ ///
+ /// The list of warnings to report as usual.
+ /// Maps to '--warnaserror- LIST-OF-WARNINGS'.
+ ///
+ public string WarningsNotAsErrors { get; set; }
+
+ ///
+ /// A list of XML root descriptor files specifying linker
+ /// roots at a granular level. See the dotnet/linker
+ /// documentation for details about the format.
+ /// Maps to '-x'.
+ ///
+ public ITaskItem[] RootDescriptorFiles { get; set; }
+
+ ///
+ /// Boolean specifying whether to enable beforefieldinit optimization globally.
+ /// Maps to '--enable-opt beforefieldinit' or '--disable-opt beforefieldinit'.
+ ///
+ public bool BeforeFieldInit { set => _beforeFieldInit = value; }
+ bool? _beforeFieldInit;
+
+ ///
+ /// Boolean specifying whether to enable overrideremoval optimization globally.
+ /// Maps to '--enable-opt overrideremoval' or '--disable-opt overrideremoval'.
+ ///
+ public bool OverrideRemoval { set => _overrideRemoval = value; }
+ bool? _overrideRemoval;
+
+ ///
+ /// Boolean specifying whether to enable unreachablebodies optimization globally.
+ /// Maps to '--enable-opt unreachablebodies' or '--disable-opt unreachablebodies'.
+ ///
+ public bool UnreachableBodies { set => _unreachableBodies = value; }
+ bool? _unreachableBodies;
+
+ ///
+ /// Boolean specifying whether to enable unusedinterfaces optimization globally.
+ /// Maps to '--enable-opt unusedinterfaces' or '--disable-opt unusedinterfaces'.
+ ///
+ public bool UnusedInterfaces { set => _unusedInterfaces = value; }
+ bool? _unusedInterfaces;
+
+ ///
+ /// Boolean specifying whether to enable ipconstprop optimization globally.
+ /// Maps to '--enable-opt ipconstprop' or '--disable-opt ipconstprop'.
+ ///
+ public bool IPConstProp { set => _iPConstProp = value; }
+ bool? _iPConstProp;
+
+ ///
+ /// A list of feature names used by the body substitution logic.
+ /// Each Item requires "Value" boolean metadata with the value of
+ /// the feature setting.
+ /// Maps to '--feature'.
+ ///
+ public ITaskItem[] FeatureSettings { get; set; }
+
+ ///
+ /// Boolean specifying whether to enable sealer optimization globally.
+ /// Maps to '--enable-opt sealer' or '--disable-opt sealer'.
+ ///
+ public bool Sealer { set => _sealer = value; }
+ bool? _sealer;
+
+ static readonly string[] _optimizationNames = new string[] {
+ "BeforeFieldInit",
+ "OverrideRemoval",
+ "UnreachableBodies",
+ "UnusedInterfaces",
+ "IPConstProp",
+ "Sealer"
+ };
+
+ ///
+ /// Custom data key-value pairs to pass to the linker.
+ /// The name of the item is the key, and the required "Value"
+ /// metadata is the value. Maps to '--custom-data key=value'.
+ ///
+ public ITaskItem[] CustomData { get; set; }
+
+ ///
+ /// Extra arguments to pass to illink, delimited by spaces.
+ ///
+ public string ExtraArgs { get; set; }
+
+ ///
+ /// Make illink dump dependencies file for linker analyzer tool.
+ /// Maps to '--dump-dependencies'.
+ ///
+ public bool DumpDependencies { get; set; }
+
+ ///
+ /// Make illink dump dependencies to the specified file type.
+ /// Maps to '--dependencies-file-format'.
+ ///
+ public string DependenciesFileFormat { get; set; }
+
+ ///
+ /// Remove debug symbols from linked assemblies.
+ /// Maps to '-b' if false.
+ /// Default if not specified is to remove symbols, like
+ /// the command-line. (Target files will likely set their own defaults to keep symbols.)
+ ///
+ public bool RemoveSymbols { set => _removeSymbols = value; }
+ bool? _removeSymbols;
+
+ ///
+ /// Sets the default action for trimmable assemblies.
+ /// Maps to '--trim-mode'
+ ///
+ public string TrimMode { get; set; }
+
+ ///
+ /// Sets the default action for assemblies which have not opted into trimming.
+ /// Maps to '--action'
+ ///
+ public string DefaultAction { get; set; }
+
+ ///
+ /// A list of custom steps to insert into the linker pipeline.
+ /// Each ItemSpec should be the path to the assembly containing the custom step.
+ /// Each Item requires "Type" metadata with the name of the custom step type.
+ /// Optional metadata:
+ /// BeforeStep: The name of a linker step. The custom step will be inserted before it.
+ /// AfterStep: The name of a linker step. The custom step will be inserted after it.
+ /// The default (if neither BeforeStep or AfterStep is specified) is to insert the
+ /// custom step at the end of the pipeline.
+ /// It is an error to specify both BeforeStep and AfterStep.
+ /// Maps to '--custom-step'.
+ ///
+ public ITaskItem[] CustomSteps { get; set; }
+
+ ///
+ /// A list selected metadata which should not be trimmed. It maps to 'keep-metadata' option
+ ///
+ public ITaskItem[] KeepMetadata { get; set; }
+
+ private const string DotNetHostPathEnvironmentName = "DOTNET_HOST_PATH";
+
+ private string _dotnetPath;
+
+ private string DotNetPath {
+ get {
+ if (!String.IsNullOrEmpty (_dotnetPath))
+ return _dotnetPath;
+
+ _dotnetPath = Environment.GetEnvironmentVariable (DotNetHostPathEnvironmentName);
+ if (String.IsNullOrEmpty (_dotnetPath))
+ throw new InvalidOperationException ($"{DotNetHostPathEnvironmentName} is not set");
+
+ return _dotnetPath;
+ }
+ }
+
+
+ /// ToolTask implementation
+
+ protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High;
+
+ protected override string ToolName => Path.GetFileName (DotNetPath);
+
+ protected override string GenerateFullPathToTool () => DotNetPath;
+
+ private string _illinkPath = "";
+
+ public string ILLinkPath {
+ get {
+ if (!String.IsNullOrEmpty (_illinkPath))
+ return _illinkPath;
+
+ var taskDirectory = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location);
+ // The linker always runs on .NET Core, even when using desktop MSBuild to host ILLink.Tasks.
+ _illinkPath = Path.Combine (Path.GetDirectoryName (taskDirectory), "net7.0", "illink.dll");
+ return _illinkPath;
+ }
+ set => _illinkPath = value;
+ }
+
+ private static string Quote (string path)
+ {
+ return $"\"{path.TrimEnd ('\\')}\"";
+ }
+
+ protected override string GenerateCommandLineCommands ()
+ {
+ var args = new StringBuilder ();
+ args.Append (Quote (ILLinkPath));
+ return args.ToString ();
+ }
+
+ private static void SetOpt (StringBuilder args, string opt, bool enabled)
+ {
+ args.Append (enabled ? "--enable-opt " : "--disable-opt ").AppendLine (opt);
+ }
+
+ private static void SetOpt (StringBuilder args, string opt, string assembly, bool enabled)
+ {
+ args.Append (enabled ? "--enable-opt " : "--disable-opt ").Append (opt).Append (' ').AppendLine (assembly);
+ }
+
+ protected override string GenerateResponseFileCommands ()
+ {
+ var args = new StringBuilder ();
+
+ if (RootDescriptorFiles != null) {
+ foreach (var rootFile in RootDescriptorFiles)
+ args.Append ("-x ").AppendLine (Quote (rootFile.ItemSpec));
+ }
+
+ foreach (var assemblyItem in RootAssemblyNames) {
+ args.Append ("-a ").Append (Quote (assemblyItem.ItemSpec));
+
+ string rootMode = assemblyItem.GetMetadata ("RootMode");
+ if (!string.IsNullOrEmpty (rootMode)) {
+ args.Append (' ');
+ args.Append (rootMode);
+ }
+
+ args.AppendLine ();
+ }
+
+ if (_singleWarn is bool generalSingleWarn) {
+ if (generalSingleWarn)
+ args.AppendLine ("--singlewarn");
+ else
+ args.AppendLine ("--singlewarn-");
+ }
+
+ string trimMode = TrimMode switch {
+ "full" => "link",
+ "partial" => "link",
+ var x => x
+ };
+ if (trimMode != null)
+ args.Append ("--trim-mode ").AppendLine (trimMode);
+
+ string defaultAction = TrimMode switch {
+ "full" => "link",
+ "partial" => "copy",
+ _ => DefaultAction
+ };
+ if (defaultAction != null)
+ args.Append ("--action ").AppendLine (defaultAction);
+
+
+ HashSet assemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase);
+ foreach (var assembly in AssemblyPaths) {
+ var assemblyPath = assembly.ItemSpec;
+ var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath);
+
+ // If there are multiple paths with the same assembly name, only use the first one.
+ if (!assemblyNames.Add (assemblyName))
+ continue;
+
+ args.Append ("-reference ").AppendLine (Quote (assemblyPath));
+
+ string assemblyTrimMode = assembly.GetMetadata ("TrimMode");
+ string isTrimmable = assembly.GetMetadata ("IsTrimmable");
+ if (string.IsNullOrEmpty (assemblyTrimMode)) {
+ if (isTrimmable.Equals ("true", StringComparison.OrdinalIgnoreCase)) {
+ // isTrimmable ~= true
+ assemblyTrimMode = trimMode;
+ } else if (isTrimmable.Equals ("false", StringComparison.OrdinalIgnoreCase)) {
+ // isTrimmable ~= false
+ assemblyTrimMode = "copy";
+ }
+ }
+ if (!string.IsNullOrEmpty (assemblyTrimMode)) {
+ args.Append ("--action ");
+ args.Append (assemblyTrimMode);
+ args.Append (' ').AppendLine (Quote (assemblyName));
+ }
+
+ // Add per-assembly optimization arguments
+ foreach (var optimization in _optimizationNames) {
+ string optimizationValue = assembly.GetMetadata (optimization);
+ if (String.IsNullOrEmpty (optimizationValue))
+ continue;
+
+ if (!Boolean.TryParse (optimizationValue, out bool enabled))
+ throw new ArgumentException ($"optimization metadata {optimization} must be True or False");
+
+ SetOpt (args, optimization, assemblyName, enabled);
+ }
+
+ // Add per-assembly verbosity arguments
+ string singleWarn = assembly.GetMetadata ("TrimmerSingleWarn");
+ if (!String.IsNullOrEmpty (singleWarn)) {
+ if (!Boolean.TryParse (singleWarn, out bool value))
+ throw new ArgumentException ($"TrimmerSingleWarn metadata must be True or False");
+
+ if (value)
+ args.Append ("--singlewarn ").AppendLine (Quote (assemblyName));
+ else
+ args.Append ("--singlewarn- ").AppendLine (Quote (assemblyName));
+ }
+ }
+
+ if (ReferenceAssemblyPaths != null) {
+ foreach (var assembly in ReferenceAssemblyPaths) {
+ var assemblyPath = assembly.ItemSpec;
+ var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath);
+
+ // Don't process references for which we already have
+ // implementation assemblies.
+ if (assemblyNames.Contains (assemblyName))
+ continue;
+
+ args.Append ("-reference ").AppendLine (Quote (assemblyPath));
+
+ // Treat reference assemblies as "skip". Ideally we
+ // would not even look at the IL, but only use them to
+ // resolve surface area.
+ args.Append ("--action skip ").AppendLine (Quote (assemblyName));
+ }
+ }
+
+ if (OutputDirectory != null)
+ args.Append ("-out ").AppendLine (Quote (OutputDirectory.ItemSpec));
+
+ if (NoWarn != null)
+ args.Append ("--nowarn ").AppendLine (Quote (NoWarn));
+
+ if (Warn != null)
+ args.Append ("--warn ").AppendLine (Quote (Warn));
+
+ if (_treatWarningsAsErrors is bool treatWarningsAsErrors && treatWarningsAsErrors)
+ args.Append ("--warnaserror ");
+ else
+ args.Append ("--warnaserror- ");
+
+ if (WarningsAsErrors != null)
+ args.Append ("--warnaserror ").AppendLine (Quote (WarningsAsErrors));
+
+ if (WarningsNotAsErrors != null)
+ args.Append ("--warnaserror- ").AppendLine (Quote (WarningsNotAsErrors));
+
+ // Add global optimization arguments
+ if (_beforeFieldInit is bool beforeFieldInit)
+ SetOpt (args, "beforefieldinit", beforeFieldInit);
+
+ if (_overrideRemoval is bool overrideRemoval)
+ SetOpt (args, "overrideremoval", overrideRemoval);
+
+ if (_unreachableBodies is bool unreachableBodies)
+ SetOpt (args, "unreachablebodies", unreachableBodies);
+
+ if (_unusedInterfaces is bool unusedInterfaces)
+ SetOpt (args, "unusedinterfaces", unusedInterfaces);
+
+ if (_iPConstProp is bool iPConstProp)
+ SetOpt (args, "ipconstprop", iPConstProp);
+
+ if (_sealer is bool sealer)
+ SetOpt (args, "sealer", sealer);
+
+ if (CustomData != null) {
+ foreach (var customData in CustomData) {
+ var key = customData.ItemSpec;
+ var value = customData.GetMetadata ("Value");
+ if (String.IsNullOrEmpty (value))
+ throw new ArgumentException ("custom data requires \"Value\" metadata");
+ args.Append ("--custom-data ").Append (' ').Append (key).Append ('=').AppendLine (Quote (value));
+ }
+ }
+
+ if (FeatureSettings != null) {
+ foreach (var featureSetting in FeatureSettings) {
+ var feature = featureSetting.ItemSpec;
+ var featureValue = featureSetting.GetMetadata ("Value");
+ if (String.IsNullOrEmpty (featureValue))
+ throw new ArgumentException ("feature settings require \"Value\" metadata");
+ args.Append ("--feature ").Append (feature).Append (' ').AppendLine (featureValue);
+ }
+ }
+
+ if (KeepMetadata != null) {
+ foreach (var metadata in KeepMetadata)
+ args.Append ("--keep-metadata ").AppendLine (Quote (metadata.ItemSpec));
+ }
+
+ if (_removeSymbols == false)
+ args.AppendLine ("-b");
+
+ if (CustomSteps != null) {
+ foreach (var customStep in CustomSteps) {
+ args.Append ("--custom-step ");
+ var stepPath = customStep.ItemSpec;
+ var stepType = customStep.GetMetadata ("Type");
+ if (stepType == null)
+ throw new ArgumentException ("custom step requires \"Type\" metadata");
+ var customStepString = $"{stepType},{stepPath}";
+
+ // handle optional before/aftersteps
+ var beforeStep = customStep.GetMetadata ("BeforeStep");
+ var afterStep = customStep.GetMetadata ("AfterStep");
+ if (!String.IsNullOrEmpty (beforeStep) && !String.IsNullOrEmpty (afterStep))
+ throw new ArgumentException ("custom step may not have both \"BeforeStep\" and \"AfterStep\" metadata");
+ if (!String.IsNullOrEmpty (beforeStep))
+ customStepString = $"-{beforeStep}:{customStepString}";
+ if (!String.IsNullOrEmpty (afterStep))
+ customStepString = $"+{afterStep}:{customStepString}";
+
+ args.AppendLine (Quote (customStepString));
+ }
+ }
+
+ if (ExtraArgs != null)
+ args.AppendLine (ExtraArgs);
+
+ if (DumpDependencies)
+ args.AppendLine ("--dump-dependencies");
+
+ if (DependenciesFileFormat != null) {
+ args.Append ("--dependencies-file-format ").AppendLine (DependenciesFileFormat);
+ }
+
+ return args.ToString ();
+ }
+ }
+}
diff --git a/src/tools/illink/src/ILLink.Tasks/build/Microsoft.NET.ILLink.targets b/src/tools/illink/src/ILLink.Tasks/build/Microsoft.NET.ILLink.targets
new file mode 100644
index 00000000000..108c5c25bd6
--- /dev/null
+++ b/src/tools/illink/src/ILLink.Tasks/build/Microsoft.NET.ILLink.targets
@@ -0,0 +1,352 @@
+
+
+
+
+
+ $(IntermediateOutputPath)linked\
+ $(IntermediateLinkDir)\
+
+ <_LinkSemaphore>$(IntermediateLinkDir)Link.semaphore
+
+
+
+
+ <_Parameter1>IsTrimmable
+ <_Parameter2>True
+
+
+
+
+
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+
+ <_EnableConsumingManagedCodeFromNativeHosting Condition="'$(EnableCppCLIHostActivation)' == 'true'">true
+ <_EnableConsumingManagedCodeFromNativeHosting Condition="'$(_EnableConsumingManagedCodeFromNativeHosting)' == ''">false
+ true
+
+
+
+
+
+ true
+
+ true
+
+ false
+
+
+
+
+ <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --notrimwarn
+ false
+
+
+
+
+ $(NoWarn);IL2121
+
+
+
+
+
+
+
+
+ <_LinkedResolvedFileToPublish Include="@(_LinkedResolvedFileToPublishCandidate)" Condition="Exists('%(Identity)')" />
+
+
+
+
+
+
+
+ <_RemovedManagedAssembly Include="@(ManagedAssemblyToLink)" Condition="!Exists('$(IntermediateLinkDir)%(Filename)%(Extension)')" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_DotNetHostDirectory>$(NetCoreRoot)
+ <_DotNetHostFileName>dotnet
+ <_DotNetHostFileName Condition="$([MSBuild]::IsOSPlatform(`Windows`))">dotnet.exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+ 0
+
+
+
+
+
+
+
+ <_ILLinkSuppressions Include="$(MSBuildThisFileDirectory)6.0_suppressions.xml" />
+
+
+
+ <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --link-attributes "@(_ILLinkSuppressions->'%(Identity)', '" --link-attributes "')"
+
+
+
+
+ copyused
+ partial
+ full
+
+
+ <_TrimmerDefaultAction Condition="$([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '7.0'))">$(TrimmerDefaultAction)
+ <_TrimmerDefaultAction Condition="'$(_TrimmerDefaultAction)' == '' And $([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '6.0'))">$(TrimMode)
+ <_TrimmerDefaultAction Condition="'$(_TrimmerDefaultAction)' == '' And $([MSBuild]::VersionEquals($(TargetFrameworkVersion), '6.0'))">copy
+
+
+
+ $(TreatWarningsAsErrors)
+ <_ExtraTrimmerArgs>--skip-unresolved true $(_ExtraTrimmerArgs)
+ true
+
+
+
+
+
+ $(NoWarn);IL2008
+ $(NoWarn);IL2009
+
+ $(NoWarn);IL2037
+
+ $(NoWarn);IL2009;IL2012
+
+
+
+
+ <_ExtraTrimmerArgs>--enable-serialization-discovery $(_ExtraTrimmerArgs)
+
+
+
+
+
+ true
+ false
+
+
+
+ <_TrimmerUnreachableBodies>false
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ copy
+
+
+
+
+
+
+ <__SingleWarnIntermediateAssembly Include="@(ManagedAssemblyToLink)" />
+ <__SingleWarnIntermediateAssembly Remove="@(IntermediateAssembly)" />
+
+ <_SingleWarnIntermediateAssembly Include="@(ManagedAssemblyToLink)" />
+ <_SingleWarnIntermediateAssembly Remove="@(__SingleWarnIntermediateAssembly)" />
+
+ <_SingleWarnIntermediateAssembly>
+ false
+
+
+
+
+
+
+
+ false
+
+
+
+
+ <_TrimmerFeatureSettings Include="@(RuntimeHostConfigurationOption)" Condition="'%(RuntimeHostConfigurationOption.Trim)' == 'true'" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <__PDBToLink Include="@(ResolvedFileToPublish)" Exclude="@(ManagedAssemblyToLink->'%(RelativeDir)%(Filename).pdb')" />
+ <_PDBToLink Include="@(ResolvedFileToPublish)" Exclude="@(__PDBToLink)" />
+
+
+
+ <_LinkedResolvedFileToPublishCandidate Include="@(ManagedAssemblyToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" />
+ <_LinkedResolvedFileToPublishCandidate Include="@(_PDBToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" />
+
+
+
+
\ No newline at end of file
diff --git a/src/tools/illink/src/linker/Linker.Steps/RootAssemblyInputStep.cs b/src/tools/illink/src/linker/Linker.Steps/RootAssemblyInputStep.cs
new file mode 100644
index 00000000000..2bc77920e40
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker.Steps/RootAssemblyInputStep.cs
@@ -0,0 +1,202 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.IO;
+using ILLink.Shared;
+using Mono.Cecil;
+
+namespace Mono.Linker.Steps
+{
+ public class RootAssemblyInput : BaseStep
+ {
+ readonly string fileName;
+ readonly AssemblyRootMode rootMode;
+
+ public RootAssemblyInput (string fileName, AssemblyRootMode rootMode)
+ {
+ this.fileName = fileName;
+ this.rootMode = rootMode;
+ }
+
+ protected override void Process ()
+ {
+ AssemblyDefinition? assembly = LoadAssemblyFile ();
+ if (assembly == null)
+ return;
+
+ var di = new DependencyInfo (DependencyKind.RootAssembly, assembly);
+ var origin = new MessageOrigin (assembly);
+
+ AssemblyAction action = Context.Annotations.GetAction (assembly);
+ switch (action) {
+ case AssemblyAction.Copy:
+ Annotations.Mark (assembly.MainModule, di, origin);
+ // Mark Step will take care of marking whole assembly
+ return;
+ case AssemblyAction.CopyUsed:
+ case AssemblyAction.Link:
+ break;
+ default:
+ Context.LogError (null, DiagnosticId.RootAssemblyCannotUseAction, assembly.Name.ToString (), action.ToString ());
+ return;
+ }
+
+ switch (rootMode) {
+ case AssemblyRootMode.EntryPoint:
+ var ep = assembly.MainModule.EntryPoint;
+ if (ep == null) {
+ Context.LogError (null, DiagnosticId.RootAssemblyDoesNotHaveEntryPoint, assembly.Name.ToString ());
+ return;
+ }
+
+ Annotations.Mark (ep.DeclaringType, di, origin);
+ Annotations.AddPreservedMethod (ep.DeclaringType, ep);
+ break;
+ case AssemblyRootMode.VisibleMembers:
+ var preserve_visible = TypePreserveMembers.Visible;
+ if (MarkInternalsVisibleTo (assembly))
+ preserve_visible |= TypePreserveMembers.Internal;
+
+ MarkAndPreserve (assembly, preserve_visible);
+ break;
+
+ case AssemblyRootMode.Library:
+ var preserve_library = TypePreserveMembers.Visible | TypePreserveMembers.Library;
+ if (MarkInternalsVisibleTo (assembly))
+ preserve_library |= TypePreserveMembers.Internal;
+
+ MarkAndPreserve (assembly, preserve_library);
+
+ // Assembly root mode wins over any enabled optimization which
+ // could conflict with library rooting behaviour
+ Context.Optimizations.Disable (
+ CodeOptimizations.Sealer |
+ CodeOptimizations.UnusedTypeChecks |
+ CodeOptimizations.UnreachableBodies |
+ CodeOptimizations.UnusedInterfaces |
+ CodeOptimizations.RemoveDescriptors |
+ CodeOptimizations.RemoveLinkAttributes |
+ CodeOptimizations.RemoveSubstitutions |
+ CodeOptimizations.RemoveDynamicDependencyAttribute |
+ CodeOptimizations.OptimizeTypeHierarchyAnnotations, assembly.Name.Name);
+
+ // Enable EventSource special handling
+ Context.DisableEventSourceSpecialHandling = false;
+
+ // No metadata trimming
+ Context.MetadataTrimming = MetadataTrimming.None;
+ break;
+ case AssemblyRootMode.AllMembers:
+ Context.Annotations.SetAction (assembly, AssemblyAction.Copy);
+ return;
+ }
+ }
+
+ AssemblyDefinition? LoadAssemblyFile ()
+ {
+ AssemblyDefinition? assembly;
+
+ if (File.Exists (fileName)) {
+ assembly = Context.Resolver.GetAssembly (fileName);
+ AssemblyDefinition? loaded = Context.GetLoadedAssembly (assembly.Name.Name);
+
+ // The same assembly could be already loaded if there are multiple inputs pointing to same file
+ if (loaded != null)
+ return loaded;
+
+ Context.Resolver.CacheAssembly (assembly);
+ return assembly;
+ }
+
+ //
+ // Quirks mode for netcore to support passing ambiguous assembly name
+ //
+ assembly = Context.TryResolve (fileName);
+ if (assembly == null)
+ Context.LogError (null, DiagnosticId.RootAssemblyCouldNotBeFound, fileName);
+
+ return assembly;
+ }
+
+ void MarkAndPreserve (AssemblyDefinition assembly, TypePreserveMembers preserve)
+ {
+ var module = assembly.MainModule;
+ if (module.HasExportedTypes)
+ foreach (var type in module.ExportedTypes)
+ MarkAndPreserve (assembly, type, preserve);
+
+ foreach (var type in module.Types)
+ MarkAndPreserve (type, preserve);
+ }
+
+ void MarkAndPreserve (TypeDefinition type, TypePreserveMembers preserve)
+ {
+ TypePreserveMembers preserve_anything = preserve;
+ if ((preserve & TypePreserveMembers.Visible) != 0 && !IsTypeVisible (type))
+ preserve_anything &= ~TypePreserveMembers.Visible;
+
+ if ((preserve & TypePreserveMembers.Internal) != 0 && IsTypePrivate (type))
+ preserve_anything &= ~TypePreserveMembers.Internal;
+
+ // Keep all interfaces and interface members in library mode
+ if ((preserve & TypePreserveMembers.Library) != 0 && type.IsInterface) {
+ Annotations.Mark (type, new DependencyInfo (DependencyKind.RootAssembly, type.Module.Assembly), new MessageOrigin (type.Module.Assembly));
+ Annotations.SetPreserve (type, TypePreserve.All);
+ }
+
+ switch (preserve_anything) {
+ case 0:
+ return;
+ case TypePreserveMembers.Library:
+ //
+ // In library mode private type can have members kept for serialization if
+ // the type is referenced
+ //
+ preserve = preserve_anything;
+ Annotations.SetMembersPreserve (type, preserve);
+ break;
+ default:
+ Annotations.Mark (type, new DependencyInfo (DependencyKind.RootAssembly, type.Module.Assembly), new MessageOrigin (type.Module.Assembly));
+ Annotations.SetMembersPreserve (type, preserve);
+ break;
+ }
+
+ if (!type.HasNestedTypes)
+ return;
+
+ foreach (TypeDefinition nested in type.NestedTypes)
+ MarkAndPreserve (nested, preserve);
+ }
+
+ void MarkAndPreserve (AssemblyDefinition assembly, ExportedType type, TypePreserveMembers preserve)
+ {
+ var di = new DependencyInfo (DependencyKind.RootAssembly, assembly);
+ var origin = new MessageOrigin (assembly);
+ Context.Annotations.Mark (type, di, origin);
+ Context.Annotations.Mark (assembly.MainModule, di, origin);
+ Annotations.SetMembersPreserve (type, preserve);
+ }
+
+ static bool IsTypeVisible (TypeDefinition type)
+ {
+ return type.IsPublic || type.IsNestedPublic || type.IsNestedFamily || type.IsNestedFamilyOrAssembly;
+ }
+
+ static bool IsTypePrivate (TypeDefinition type)
+ {
+ return type.IsNestedPrivate;
+ }
+
+ bool MarkInternalsVisibleTo (AssemblyDefinition assembly)
+ {
+ foreach (CustomAttribute attribute in assembly.CustomAttributes) {
+ if (attribute.Constructor.DeclaringType.IsTypeOf ("System.Runtime.CompilerServices", "InternalsVisibleToAttribute")) {
+ Context.Annotations.Mark (attribute, new DependencyInfo (DependencyKind.RootAssembly, assembly));
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/tools/illink/src/linker/Linker/AssemblyRootMode.cs b/src/tools/illink/src/linker/Linker/AssemblyRootMode.cs
new file mode 100644
index 00000000000..c9a3d98a604
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker/AssemblyRootMode.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Mono.Linker
+{
+ public enum AssemblyRootMode
+ {
+ AllMembers = 0,
+ EntryPoint,
+ VisibleMembers,
+ Library
+ }
+}
diff --git a/src/tools/illink/src/linker/Linker/Driver.cs b/src/tools/illink/src/linker/Linker/Driver.cs
new file mode 100644
index 00000000000..7b9e10bdda6
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker/Driver.cs
@@ -0,0 +1,1422 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+//
+// Driver.cs
+//
+// Author:
+// Jb Evain (jbevain@gmail.com)
+//
+// (C) 2006 Jb Evain
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reflection;
+using System.Runtime.Loader;
+using System.Text;
+using ILLink.Shared;
+using Mono.Cecil;
+using Mono.Linker.Steps;
+
+namespace Mono.Linker
+{
+
+ public partial class Driver : IDisposable
+ {
+ const string resolvers = "-a|-x";
+ const string _linker = "IL Linker";
+
+ public static int Main (string[] args)
+ {
+ LinkerEventSource.Log.LinkerStart (string.Join ("; ", args));
+ if (args.Length == 0) {
+ Console.Error.WriteLine ("No parameters specified");
+ LinkerEventSource.Log.LinkerStop ();
+ return 1;
+ }
+
+ if (!ProcessResponseFile (args, out var arguments)) {
+ LinkerEventSource.Log.LinkerStop ();
+ return 1;
+ }
+
+ try {
+ using (Driver driver = new Driver (arguments)) {
+ return driver.Run ();
+ }
+ } catch {
+ Console.Error.WriteLine ("Fatal error in {0}", _linker);
+ throw;
+ } finally {
+ LinkerEventSource.Log.LinkerStop ();
+ }
+ }
+
+ readonly Queue arguments;
+ bool _needAddBypassNGenStep;
+ LinkContext? context;
+ protected LinkContext Context {
+ get {
+ Debug.Assert (context != null);
+ return context;
+ }
+ }
+
+ public Driver (Queue arguments)
+ {
+ this.arguments = arguments;
+ }
+
+ public static bool ProcessResponseFile (string[] args, out Queue result)
+ {
+ result = new Queue ();
+ foreach (string arg in args) {
+ if (arg.StartsWith ("@")) {
+ try {
+ string responseFileName = arg.Substring (1);
+ using (var responseFileText = new StreamReader (responseFileName))
+ ParseResponseFile (responseFileText, result);
+ } catch (Exception e) when (e is IOException or ObjectDisposedException) {
+ Console.Error.WriteLine ("Cannot read response file due to '{0}'", e.Message);
+ return false;
+ }
+ } else {
+ result.Enqueue (arg);
+ }
+ }
+
+ return true;
+ }
+
+ public static void ParseResponseFile (TextReader reader, Queue result)
+ {
+ int cur;
+ while ((cur = reader.Read ()) >= 0) {
+ // skip whitespace
+ while (char.IsWhiteSpace ((char) cur)) {
+ if ((cur = reader.Read ()) < 0)
+ break;
+ }
+ if (cur < 0) // EOF
+ break;
+
+ StringBuilder argBuilder = new StringBuilder ();
+ bool inquote = false;
+
+ // build up an argument one character at a time
+ while (true) {
+ bool copyChar = true;
+ int numBackslash = 0;
+
+ // count backslashes
+ while (cur == '\\') {
+ numBackslash++;
+ if ((cur = reader.Read ()) < 0)
+ break;
+ }
+ if (cur == '"') {
+ if ((numBackslash % 2) == 0) {
+ if (inquote && (reader.Peek () == '"')) {
+ // handle "" escape sequence in a quote
+ cur = reader.Read ();
+ } else {
+ // unescaped " begins/ends a quote
+ copyChar = false;
+ inquote = !inquote;
+ }
+ }
+ // treat backslashes before " as escapes
+ numBackslash /= 2;
+ }
+ if (numBackslash > 0)
+ argBuilder.Append (new String ('\\', numBackslash));
+ if (cur < 0 || (!inquote && char.IsWhiteSpace ((char) cur)))
+ break;
+ if (copyChar)
+ argBuilder.Append ((char) cur);
+ cur = reader.Read ();
+ }
+ result.Enqueue (argBuilder.ToString ());
+ }
+ }
+
+ void ErrorMissingArgument (string optionName)
+ {
+ Context.LogError (null, DiagnosticId.MissingArgumentForCommanLineOptionName, optionName);
+ }
+
+ public enum DependenciesFileFormat { Xml, Dgml };
+
+ // Perform setup of the LinkContext and parse the arguments.
+ // Return values:
+ // 0 => successfully set up context with all arguments
+ // 1 => argument processing stopped early without errors
+ // -1 => error setting up context
+ protected int SetupContext (ILogger? customLogger = null)
+ {
+ Pipeline p = GetStandardPipeline ();
+ context = GetDefaultContext (p, customLogger);
+
+ var body_substituter_steps = new Stack ();
+ var xml_custom_attribute_steps = new Stack ();
+ var custom_steps = new List ();
+ var set_optimizations = new List<(CodeOptimizations, string?, bool)> ();
+ bool dumpDependencies = false;
+ string? dependenciesFileName = null;
+ context.StripSecurity = true;
+ bool new_mvid_used = false;
+ bool deterministic_used = false;
+ bool keepCompilersResources = false;
+ MetadataTrimming metadataTrimming = MetadataTrimming.Any;
+ DependenciesFileFormat fileType = DependenciesFileFormat.Xml;
+
+ List inputs = CreateDefaultResolvers ();
+
+ while (arguments.Count > 0) {
+ string token = arguments.Dequeue ();
+ if (token.Length < 2) {
+ context.LogError (null, DiagnosticId.UnrecognizedCommandLineOption, token);
+ return -1;
+ }
+
+ //
+ // Handling of --value like options
+ //
+ if (token[0] == '-' && token[1] == '-') {
+ switch (token) {
+ case "--help":
+ Usage ();
+ return 1;
+
+ case "--skip-unresolved":
+ if (!GetBoolParam (token, l => context.IgnoreUnresolved = l))
+ return -1;
+
+ continue;
+
+ case "--verbose":
+ context.LogMessages = true;
+ continue;
+
+ case "--dependencies-file":
+ if (!GetStringParam (token, out dependenciesFileName))
+ return -1;
+
+ continue;
+
+ case "--dump-dependencies":
+ dumpDependencies = true;
+ continue;
+
+ case "--dependencies-file-format":
+ if (!GetStringParam (token, out var dependenciesFileFormat))
+ return -1;
+
+ if (!Enum.TryParse (dependenciesFileFormat, ignoreCase: true, out fileType)) {
+ context.LogError (null, DiagnosticId.InvalidDependenciesFileFormat);
+ return -1;
+ }
+ continue;
+
+ case "--reduced-tracing":
+ if (!GetBoolParam (token, l => context.EnableReducedTracing = l))
+ return -1;
+
+ continue;
+
+ case "--used-attrs-only":
+ if (!GetBoolParam (token, l => context.KeepUsedAttributeTypesOnly = l))
+ return -1;
+
+ continue;
+
+ case "--strip-security":
+ if (!GetBoolParam (token, l => context.StripSecurity = l))
+ return -1;
+
+ continue;
+
+ case "--strip-descriptors":
+ if (!GetBoolParam (token, l => set_optimizations.Add ((CodeOptimizations.RemoveDescriptors, null, l))))
+ return -1;
+
+ continue;
+
+ case "--strip-substitutions":
+ if (!GetBoolParam (token, l => set_optimizations.Add ((CodeOptimizations.RemoveSubstitutions, null, l))))
+ return -1;
+
+ continue;
+
+ case "--strip-link-attributes":
+ if (!GetBoolParam (token, l => set_optimizations.Add ((CodeOptimizations.RemoveLinkAttributes, null, l))))
+ return -1;
+
+ continue;
+
+ case "--substitutions":
+ if (arguments.Count < 1) {
+ ErrorMissingArgument (token);
+ return -1;
+ }
+
+ if (!GetStringParam (token, out string? substitutionFile))
+ return -1;
+
+ body_substituter_steps.Push (substitutionFile);
+
+ continue;
+ case "--explicit-reflection":
+ if (!GetBoolParam (token, l => context.AddReflectionAnnotations = l))
+ return -1;
+
+ continue;
+
+ case "--action": {
+ if (!GetStringParam (token, out string? actionString))
+ return -1;
+
+ AssemblyAction? action = ParseAssemblyAction (actionString);
+ if (action == null)
+ return -1;
+
+ string? assemblyName = GetNextStringValue ();
+ if (assemblyName == null) {
+ context.DefaultAction = action.Value;
+ continue;
+ }
+
+ if (!IsValidAssemblyName (assemblyName)) {
+ context.LogError (null, DiagnosticId.InvalidAssemblyName, assemblyName);
+ return -1;
+ }
+
+ context.RegisterAssemblyAction (assemblyName, action.Value);
+ continue;
+ }
+ case "--trim-mode": {
+ if (!GetStringParam (token, out string? actionString))
+ return -1;
+
+ AssemblyAction? action = ParseAssemblyAction (actionString);
+ if (action == null)
+ return -1;
+
+ context.TrimAction = action.Value;
+ continue;
+ }
+ case "--custom-step":
+ if (!GetStringParam (token, out string? custom_step))
+ return -1;
+
+ custom_steps.Add (custom_step);
+
+ continue;
+
+ case "--custom-data":
+ if (arguments.Count < 1) {
+ ErrorMissingArgument (token);
+ return -1;
+ }
+
+ var arg = arguments.Dequeue ();
+ string[] values = arg.Split ('=');
+ if (values?.Length != 2) {
+ context.LogError (null, DiagnosticId.CustomDataFormatIsInvalid);
+ return -1;
+ }
+
+ context.SetCustomData (values[0], values[1]);
+ continue;
+
+ case "--keep-compilers-resources":
+ if (!GetBoolParam (token, l => keepCompilersResources = l))
+ return -1;
+
+ continue;
+
+ case "--keep-dep-attributes":
+ if (!GetBoolParam (token, l => set_optimizations.Add ((CodeOptimizations.RemoveDynamicDependencyAttribute, null, !l))))
+ return -1;
+
+ continue;
+
+ case "--keep-metadata": {
+ if (!GetStringParam (token, out string? mname))
+ return -1;
+
+ if (!TryGetMetadataTrimming (mname, out var type))
+ return -1;
+
+ metadataTrimming &= ~type;
+ continue;
+ }
+
+ case "--enable-serialization-discovery":
+ if (!GetBoolParam (token, l => context.EnableSerializationDiscovery = l))
+ return -1;
+
+ continue;
+
+ case "--disable-operator-discovery":
+ if (!GetBoolParam (token, l => context.DisableOperatorDiscovery = l))
+ return -1;
+
+ continue;
+
+ case "--ignore-descriptors":
+ if (!GetBoolParam (token, l => context.IgnoreDescriptors = l))
+ return -1;
+
+ continue;
+
+ case "--ignore-substitutions":
+ if (!GetBoolParam (token, l => context.IgnoreSubstitutions = l))
+ return -1;
+
+ continue;
+
+ case "--ignore-link-attributes":
+ if (!GetBoolParam (token, l => context.IgnoreLinkAttributes = l))
+ return -1;
+
+ continue;
+
+ case "--disable-opt": {
+ if (!GetStringParam (token, out string? optName))
+ return -1;
+
+ if (!GetOptimizationName (optName, out var opt))
+ return -1;
+
+ string? assemblyName = GetNextStringValue ();
+ set_optimizations.Add ((opt, assemblyName, false));
+
+ continue;
+ }
+ case "--enable-opt": {
+ if (!GetStringParam (token, out string? optName))
+ return -1;
+
+ if (!GetOptimizationName (optName, out var opt))
+ return -1;
+
+ string? assemblyName = GetNextStringValue ();
+ set_optimizations.Add ((opt, assemblyName, true));
+
+ continue;
+ }
+
+ case "--feature": {
+ if (!GetStringParam (token, out string? featureName))
+ return -1;
+
+ if (!GetBoolParam (token, value => {
+ context.SetFeatureValue (featureName, value);
+ }))
+ return -1;
+
+ continue;
+ }
+ case "--new-mvid":
+ //
+ // This is not same as --deterministic which calculates MVID
+ // from stable assembly content. This option creates a new random
+ // mvid or uses mvid of the source assembly.
+ //
+ if (!GetBoolParam (token, l => {
+ if (!l)
+ p.RemoveStep (typeof (RegenerateGuidStep));
+ }))
+ return -1;
+
+ new_mvid_used = true;
+ continue;
+
+ case "--deterministic":
+ if (!GetBoolParam (token, l => context.DeterministicOutput = l))
+ return -1;
+
+ deterministic_used = true;
+ continue;
+
+ case "--output-assemblylist":
+ if (!GetStringParam (token, out string? assemblyListFile))
+ return -1;
+
+ context.AssemblyListFile = assemblyListFile;
+
+ continue;
+
+ case "--output-pinvokes":
+ if (!GetStringParam (token, out string? pinvokesListFile))
+ return -1;
+
+ context.PInvokesListFile = pinvokesListFile;
+
+ continue;
+
+ case "--link-attributes":
+ if (arguments.Count < 1) {
+ ErrorMissingArgument (token);
+ return -1;
+ }
+
+ if (!GetStringParam (token, out string? fileList))
+ return -1;
+
+ foreach (string file in GetFiles (fileList))
+ xml_custom_attribute_steps.Push (file);
+
+ continue;
+
+ case "--generate-warning-suppressions":
+ if (!GetStringParam (token, out string? generateWarningSuppressionsArgument))
+ return -1;
+
+ if (!GetWarningSuppressionWriterFileOutputKind (generateWarningSuppressionsArgument, out var fileOutputKind)) {
+ context.LogError (null, DiagnosticId.InvalidGenerateWarningSuppressionsValue, generateWarningSuppressionsArgument);
+ return -1;
+ }
+
+ context.WarningSuppressionWriter = new WarningSuppressionWriter (context, fileOutputKind);
+ continue;
+
+ case "--notrimwarn":
+ context.NoTrimWarn = true;
+ continue;
+
+ case "--nowarn":
+ if (!GetStringParam (token, out string? noWarnArgument))
+ return -1;
+
+ context.NoWarn.UnionWith (ProcessWarningCodes (noWarnArgument));
+ continue;
+
+ case "--warnaserror":
+ case "--warnaserror+":
+ var warningList = GetNextStringValue ();
+ if (!string.IsNullOrEmpty (warningList)) {
+ foreach (var warning in ProcessWarningCodes (warningList))
+ context.WarnAsError[warning] = true;
+
+ } else {
+ context.GeneralWarnAsError = true;
+ context.WarnAsError.Clear ();
+ }
+
+ continue;
+
+ case "--warnaserror-":
+ warningList = GetNextStringValue ();
+ if (!string.IsNullOrEmpty (warningList)) {
+ foreach (var warning in ProcessWarningCodes (warningList))
+ context.WarnAsError[warning] = false;
+
+ } else {
+ context.GeneralWarnAsError = false;
+ context.WarnAsError.Clear ();
+ }
+
+ continue;
+
+ case "--warn":
+ if (!GetStringParam (token, out string? warnVersionArgument))
+ return -1;
+
+ if (!GetWarnVersion (warnVersionArgument, out WarnVersion version))
+ return -1;
+
+ context.WarnVersion = version;
+
+ continue;
+
+ case "--singlewarn":
+ case "--singlewarn+": {
+ string? assemblyName = GetNextStringValue ();
+ if (assemblyName != null) {
+ if (!IsValidAssemblyName (assemblyName)) {
+ context.LogError (null, DiagnosticId.InvalidAssemblyName, assemblyName);
+ return -1;
+ }
+
+ context.SingleWarn[assemblyName] = true;
+ } else {
+ context.GeneralSingleWarn = true;
+ context.SingleWarn.Clear ();
+ }
+
+ continue;
+ }
+
+ case "--singlewarn-": {
+ string? assemblyName = GetNextStringValue ();
+ if (assemblyName != null) {
+ if (!IsValidAssemblyName (assemblyName)) {
+ context.LogError (null, DiagnosticId.InvalidAssemblyName, assemblyName);
+ return -1;
+ }
+
+ context.SingleWarn[assemblyName] = false;
+ } else {
+ context.GeneralSingleWarn = false;
+ context.SingleWarn.Clear ();
+ }
+
+ continue;
+ }
+
+ case "--version":
+ Version ();
+ return 1;
+
+ case "--about":
+ About ();
+ return 1;
+ }
+ }
+
+ if (token[0] == '-' || token[1] == '/') {
+
+ switch (token.Substring (1)) {
+ case "d":
+ if (!GetStringParam (token, out string? directory))
+ return -1;
+
+ DirectoryInfo info = new DirectoryInfo (directory);
+ context.Resolver.AddSearchDirectory (info.FullName);
+
+ continue;
+ case "o":
+ case "out":
+ if (!GetStringParam (token, out string? outputDirectory))
+ return -1;
+
+ context.OutputDirectory = outputDirectory;
+
+ continue;
+ case "x": {
+ if (!GetStringParam (token, out string? xmlFile))
+ return -1;
+
+ if (!File.Exists (xmlFile)) {
+ context.LogError (null, DiagnosticId.XmlDescriptorCouldNotBeFound, xmlFile);
+ return -1;
+ }
+
+ inputs.Add (new ResolveFromXmlStep (File.OpenRead (xmlFile), xmlFile));
+ continue;
+ }
+ case "a": {
+ if (!GetStringParam (token, out string? assemblyFile))
+ return -1;
+
+ if (!File.Exists (assemblyFile) && assemblyFile.EndsWith (".dll", StringComparison.InvariantCultureIgnoreCase)) {
+ context.LogError (null, DiagnosticId.RootAssemblyCouldNotBeFound, assemblyFile);
+ return -1;
+ }
+
+ AssemblyRootMode rmode = AssemblyRootMode.AllMembers;
+ var rootMode = GetNextStringValue ();
+ if (rootMode != null) {
+ var parsed_rmode = ParseAssemblyRootMode (rootMode);
+ if (parsed_rmode is null)
+ return -1;
+
+ rmode = parsed_rmode.Value;
+ }
+
+ inputs.Add (new RootAssemblyInput (assemblyFile, rmode));
+ continue;
+ }
+ case "b":
+ if (!GetBoolParam (token, l => context.LinkSymbols = l))
+ return -1;
+
+ continue;
+ case "g":
+ if (!GetBoolParam (token, l => context.DeterministicOutput = !l))
+ return -1;
+
+ continue;
+ case "z":
+ if (!GetBoolParam (token, l => context.IgnoreDescriptors = !l))
+ return -1;
+
+ continue;
+ case "?":
+ case "h":
+ case "help":
+ Usage ();
+ return 1;
+
+ case "reference":
+ if (!GetStringParam (token, out string? reference))
+ return -1;
+
+ context.Resolver.AddReferenceAssembly (reference);
+
+ continue;
+ }
+ }
+
+ context.LogError (null, DiagnosticId.UnrecognizedCommandLineOption, token);
+ return -1;
+ }
+
+ if (inputs.Count == 0) {
+ context.LogError (null, DiagnosticId.NoFilesToLinkSpecified, resolvers);
+ return -1;
+ }
+
+ if (new_mvid_used && deterministic_used) {
+ context.LogError (null, DiagnosticId.NewMvidAndDeterministicCannotBeUsedAtSameTime);
+ return -1;
+ }
+
+ context.MetadataTrimming = metadataTrimming;
+
+ // Default to deterministic output
+ if (!new_mvid_used && !deterministic_used) {
+ context.DeterministicOutput = true;
+ }
+ if (dumpDependencies) {
+ switch (fileType) {
+ case DependenciesFileFormat.Xml:
+ AddXmlDependencyRecorder (context, dependenciesFileName);
+ break;
+ case DependenciesFileFormat.Dgml:
+ AddDgmlDependencyRecorder (context, dependenciesFileName);
+ break;
+ default:
+ context.LogError (null, DiagnosticId.InvalidDependenciesFileFormat);
+ break;
+ }
+ }
+
+
+ if (set_optimizations.Count > 0) {
+ foreach (var (opt, assemblyName, enable) in set_optimizations) {
+ if (enable)
+ context.Optimizations.Enable (opt, assemblyName);
+ else
+ context.Optimizations.Disable (opt, assemblyName);
+ }
+ }
+
+ //
+ // Modify the default pipeline
+ //
+
+ for (int i = inputs.Count; i != 0; --i)
+ p.PrependStep (inputs[i - 1]);
+
+ foreach (var file in xml_custom_attribute_steps)
+ AddLinkAttributesStep (p, file);
+
+ foreach (var file in body_substituter_steps)
+ AddBodySubstituterStep (p, file);
+
+ if (context.DeterministicOutput)
+ p.RemoveStep (typeof (RegenerateGuidStep));
+
+ if (context.AddReflectionAnnotations)
+ p.AddStepAfter (typeof (MarkStep), new ReflectionBlockedStep ());
+
+ if (_needAddBypassNGenStep)
+ p.AddStepAfter (typeof (SweepStep), new AddBypassNGenStep ());
+
+ if (keepCompilersResources) {
+ p.RemoveStep (typeof (RemoveResourcesStep));
+ }
+
+ p.AddStepBefore (typeof (OutputStep), new SealerStep ());
+
+ //
+ // Pipeline setup with all steps enabled
+ //
+ // RootAssemblyInputStep or ResolveFromXmlStep [at least one of them]
+ // LinkAttributesStep [optional, possibly many]
+ // BodySubstituterStep [optional]
+ // MarkStep
+ // ReflectionBlockedStep [optional]
+ // RemoveResourcesStep [optional]
+ // ProcessWarningsStep
+ // OutputWarningSuppressions
+ // SweepStep
+ // AddBypassNGenStep [optional]
+ // CodeRewriterStep
+ // CleanStep
+ // RegenerateGuidStep [optional]
+ // SealerStep
+ // OutputStep
+
+ if (context.EnableSerializationDiscovery)
+ p.MarkHandlers.Add (new DiscoverSerializationHandler ());
+
+ if (!context.DisableOperatorDiscovery)
+ p.MarkHandlers.Add (new DiscoverOperatorsHandler ());
+
+ foreach (string custom_step in custom_steps) {
+ if (!AddCustomStep (p, custom_step))
+ return -1;
+ }
+
+ return 0;
+ }
+
+ // Returns the exit code of the process. 0 indicates success.
+ // Known non-recoverable errors (LinkerFatalErrorException) set the exit code
+ // to the error code.
+ // May propagate exceptions, which will result in the process getting an
+ // exit code determined by dotnet.
+ public int Run (ILogger? customLogger = null)
+ {
+ int setupStatus = SetupContext (customLogger);
+ if (setupStatus > 0)
+ return 0;
+ if (setupStatus < 0)
+ return 1;
+
+ Pipeline p = Context.Pipeline;
+ PreProcessPipeline (p);
+
+ try {
+ p.Process (Context);
+ } catch (Exception e) when (LogFatalError (e)) {
+ // Unreachable
+ throw;
+ }
+
+ Context.FlushCachedWarnings ();
+ Context.Tracer.Finish ();
+ return Context.ErrorsCount > 0 ? 1 : 0;
+ }
+
+ ///
+ /// This method is called in the exception filter for unexpected exceptions.
+ /// Prints error messages and returns false to avoid catching in the exception filter.
+ ///
+ bool LogFatalError (Exception e)
+ {
+ switch (e) {
+ case LinkerFatalErrorException lex:
+ Context.LogMessage (lex.MessageContainer);
+ Debug.Assert (lex.MessageContainer.Category == MessageCategory.Error);
+ Debug.Assert (lex.MessageContainer.Code != null);
+ Debug.Assert (lex.MessageContainer.Code.Value != 0);
+ break;
+ case ResolutionException re:
+ Context.LogError (null, DiagnosticId.FailedToResolveMetadataElement, re.Message);
+ break;
+ default:
+ Context.LogError (null, DiagnosticId.LinkerUnexpectedError);
+ break;
+ }
+ return false;
+ }
+
+ partial void PreProcessPipeline (Pipeline pipeline);
+
+ private static IEnumerable ProcessWarningCodes (string value)
+ {
+ string Unquote (string arg)
+ {
+ if (arg.Length > 1 && arg[0] == '"' && arg[arg.Length - 1] == '"')
+ return arg.Substring (1, arg.Length - 2);
+
+ return arg;
+ }
+
+ value = Unquote (value);
+ string[] values = value.Split (new char[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (string v in values) {
+ var id = v.Trim ();
+ if (!id.StartsWith ("IL", StringComparison.Ordinal) || !ushort.TryParse (id.AsSpan (2), out ushort code))
+ continue;
+
+ yield return code;
+ }
+ }
+
+ Assembly? GetCustomAssembly (string arg)
+ {
+ if (Path.IsPathRooted (arg)) {
+ var assemblyPath = Path.GetFullPath (arg);
+ if (File.Exists (assemblyPath)) {
+ // The CLR will return the already-loaded assembly if the same path is requested multiple times
+ // (or even if a different path specifies the "same" assembly, based on the MVID).
+
+ // Ignore warning, since we're just enabling analyzer for dogfooding
+#pragma warning disable IL2026
+ return AssemblyLoadContext.Default.LoadFromAssemblyPath (assemblyPath);
+#pragma warning restore IL2026
+ }
+ Context.LogError (null, DiagnosticId.AssemblyInCustomStepOptionCouldNotBeFound, arg);
+ } else
+ Context.LogError (null, DiagnosticId.AssemblyPathInCustomStepMustBeFullyQualified, arg);
+
+ return null;
+ }
+
+ protected virtual void AddResolveFromXmlStep (Pipeline pipeline, string file)
+ {
+ pipeline.PrependStep (new ResolveFromXmlStep (File.OpenRead (file), file));
+ }
+
+ protected virtual void AddLinkAttributesStep (Pipeline pipeline, string file)
+ {
+ pipeline.AddStepBefore (typeof (MarkStep), new LinkAttributesStep (File.OpenRead (file), file));
+ }
+
+ static void AddBodySubstituterStep (Pipeline pipeline, string file)
+ {
+ pipeline.AddStepBefore (typeof (MarkStep), new BodySubstituterStep (File.OpenRead (file), file));
+ }
+
+ protected virtual void AddXmlDependencyRecorder (LinkContext context, string? fileName)
+ {
+ context.Tracer.AddRecorder (new XmlDependencyRecorder (context, fileName));
+ }
+
+ protected virtual void AddDgmlDependencyRecorder (LinkContext context, string? fileName)
+ {
+ context.Tracer.AddRecorder (new DgmlDependencyRecorder (context, fileName));
+ }
+
+ protected bool AddMarkHandler (Pipeline pipeline, string arg)
+ {
+ if (!TryGetCustomAssembly (ref arg, out Assembly? custom_assembly))
+ return false;
+
+ var step = ResolveStep (arg, custom_assembly);
+ if (step == null)
+ return false;
+
+ pipeline.AppendMarkHandler (step);
+ return true;
+ }
+
+ bool TryGetCustomAssembly (ref string arg, [NotNullWhen (true)] out Assembly? assembly)
+ {
+ assembly = null;
+ int pos = arg.IndexOf (",");
+ if (pos == -1)
+ return false;
+
+ assembly = GetCustomAssembly (arg.Substring (pos + 1));
+ if (assembly == null)
+ return false;
+
+ arg = arg.Substring (0, pos);
+ return true;
+ }
+
+ protected bool AddCustomStep (Pipeline pipeline, string arg)
+ {
+ if (!TryGetCustomAssembly (ref arg, out Assembly? custom_assembly))
+ return false;
+
+ string customStepName;
+ string? targetName = null;
+ bool before = false;
+ if (!arg.Contains (':')) {
+ customStepName = arg;
+ } else {
+ string[] parts = arg.Split (':');
+ if (parts.Length != 2) {
+ Context.LogError (null, DiagnosticId.InvalidArgForCustomStep, arg);
+ return false;
+ }
+ customStepName = parts[1];
+
+ if (!parts[0].StartsWith ("-") && !parts[0].StartsWith ("+")) {
+ Context.LogError (null, DiagnosticId.ExpectedSignToControlNewStepInsertion);
+ return false;
+ }
+
+ before = parts[0][0] == '-';
+ targetName = parts[0].Substring (1);
+ }
+
+ var stepType = ResolveStepType (customStepName, custom_assembly);
+ if (stepType == null)
+ return false;
+
+ if (typeof (IStep).IsAssignableFrom (stepType)) {
+
+ var customStep = (IStep?) Activator.CreateInstance (stepType) ?? throw new InvalidOperationException ();
+ if (targetName == null) {
+ pipeline.AppendStep (customStep);
+ return true;
+ }
+
+ IStep? target = FindStep (pipeline, targetName);
+ if (target == null) {
+ Context.LogError (null, DiagnosticId.PipelineStepCouldNotBeFound, targetName);
+ return false;
+ }
+
+ if (before)
+ pipeline.AddStepBefore (target, customStep);
+ else
+ pipeline.AddStepAfter (target, customStep);
+
+ return true;
+ }
+
+ if (typeof (IMarkHandler).IsAssignableFrom (stepType)) {
+
+ var customStep = (IMarkHandler?) Activator.CreateInstance (stepType) ?? throw new InvalidOperationException ();
+ if (targetName == null) {
+ pipeline.AppendMarkHandler (customStep);
+ return true;
+ }
+
+ IMarkHandler? target = FindMarkHandler (pipeline, targetName);
+ if (target == null) {
+ Context.LogError (null, DiagnosticId.PipelineStepCouldNotBeFound, targetName);
+ return false;
+ }
+
+ if (before)
+ pipeline.AddMarkHandlerBefore (target, customStep);
+ else
+ pipeline.AddMarkHandlerAfter (target, customStep);
+
+ return true;
+ }
+
+ Context.LogError (null, DiagnosticId.CustomStepTypeIsIncompatibleWithLinkerVersion, stepType.ToString ());
+ return false;
+ }
+
+ protected virtual IStep? FindStep (Pipeline pipeline, string name)
+ {
+ foreach (IStep step in pipeline.GetSteps ()) {
+ Type t = step.GetType ();
+ if (t.Name == name)
+ return step;
+ }
+
+ return null;
+ }
+
+ static IMarkHandler? FindMarkHandler (Pipeline pipeline, string name)
+ {
+ foreach (IMarkHandler step in pipeline.MarkHandlers) {
+ Type t = step.GetType ();
+ if (t.Name == name)
+ return step;
+ }
+
+ return null;
+ }
+
+ Type? ResolveStepType (string type, Assembly assembly)
+ {
+ // Ignore warning, since we're just enabling analyzer for dogfooding
+#pragma warning disable IL2026
+ Type? step = assembly != null ? assembly.GetType (type) : Type.GetType (type, false);
+#pragma warning restore IL2026
+
+ if (step == null) {
+ Context.LogError (null, DiagnosticId.CustomStepTypeCouldNotBeFound, type);
+ return null;
+ }
+
+ return step;
+ }
+
+ TStep? ResolveStep (string type, Assembly assembly) where TStep : class
+ {
+ // Ignore warning, since we're just enabling analyzer for dogfooding
+#pragma warning disable IL2026
+ Type? step = assembly != null ? assembly.GetType (type) : Type.GetType (type, false);
+#pragma warning restore IL2026
+
+ if (step == null) {
+ Context.LogError (null, DiagnosticId.CustomStepTypeCouldNotBeFound, type);
+ return null;
+ }
+
+ if (!typeof (TStep).IsAssignableFrom (step)) {
+ Context.LogError (null, DiagnosticId.CustomStepTypeIsIncompatibleWithLinkerVersion, type);
+ return null;
+ }
+
+ return (TStep?) Activator.CreateInstance (step);
+ }
+
+ static string[] GetFiles (string param)
+ {
+ if (param.Length < 1 || param[0] != '@')
+ return new string[] { param };
+
+ string file = param.Substring (1);
+ return ReadLines (file);
+ }
+
+ static string[] ReadLines (string file)
+ {
+ var lines = new List ();
+ using (StreamReader reader = new StreamReader (file)) {
+ string? line;
+ while ((line = reader.ReadLine ()) != null)
+ lines.Add (line);
+ }
+ return lines.ToArray ();
+ }
+
+ AssemblyAction? ParseAssemblyAction (string s)
+ {
+ switch (s.ToLowerInvariant ()) {
+ case "copy":
+ return AssemblyAction.Copy;
+ case "copyused":
+ return AssemblyAction.CopyUsed;
+ case "link":
+ return AssemblyAction.Link;
+ case "skip":
+ return AssemblyAction.Skip;
+
+ case "addbypassngen":
+ _needAddBypassNGenStep = true;
+ return AssemblyAction.AddBypassNGen;
+ case "addbypassngenused":
+ _needAddBypassNGenStep = true;
+ return AssemblyAction.AddBypassNGenUsed;
+ }
+
+ Context.LogError (null, DiagnosticId.InvalidAssemblyAction, s);
+ return null;
+ }
+
+ AssemblyRootMode? ParseAssemblyRootMode (string s)
+ {
+ switch (s.ToLowerInvariant ()) {
+ case "all":
+ return AssemblyRootMode.AllMembers;
+ case "visible":
+ return AssemblyRootMode.VisibleMembers;
+ case "entrypoint":
+ return AssemblyRootMode.EntryPoint;
+ case "library":
+ return AssemblyRootMode.Library;
+ }
+
+ Context.LogError (null, DiagnosticId.InvalidAssemblyRootMode, s);
+ return null;
+ }
+
+ bool GetWarnVersion (string text, out WarnVersion version)
+ {
+ if (int.TryParse (text, out int versionNum)) {
+ version = (WarnVersion) versionNum;
+ if (version >= WarnVersion.ILLink0 && version <= WarnVersion.Latest)
+ return true;
+ }
+
+ Context.LogError (null, DiagnosticId.InvalidWarningVersion, text);
+ version = 0;
+ return false;
+ }
+
+ protected bool GetOptimizationName (string text, out CodeOptimizations optimization)
+ {
+ switch (text.ToLowerInvariant ()) {
+ case "beforefieldinit":
+ optimization = CodeOptimizations.BeforeFieldInit;
+ return true;
+ case "overrideremoval":
+ optimization = CodeOptimizations.OverrideRemoval;
+ return true;
+ case "unreachablebodies":
+ optimization = CodeOptimizations.UnreachableBodies;
+ return true;
+ case "unusedinterfaces":
+ optimization = CodeOptimizations.UnusedInterfaces;
+ return true;
+ case "unusedtypechecks":
+ optimization = CodeOptimizations.UnusedTypeChecks;
+ return true;
+ case "ipconstprop":
+ optimization = CodeOptimizations.IPConstantPropagation;
+ return true;
+ case "sealer":
+ optimization = CodeOptimizations.Sealer;
+ return true;
+ }
+
+ Context.LogError (null, DiagnosticId.InvalidOptimizationValue, text);
+ optimization = 0;
+ return false;
+ }
+
+ bool TryGetMetadataTrimming (string text, out MetadataTrimming metadataTrimming)
+ {
+ switch (text.ToLowerInvariant ()) {
+ case "all":
+ metadataTrimming = MetadataTrimming.Any;
+ return true;
+ case "none":
+ metadataTrimming = MetadataTrimming.None;
+ return true;
+ case "parametername":
+ metadataTrimming = MetadataTrimming.ParameterName;
+ return true;
+ }
+
+ Context.LogError (null, DiagnosticId.InvalidMetadataOption, text);
+ metadataTrimming = 0;
+ return false;
+ }
+
+ protected static bool GetWarningSuppressionWriterFileOutputKind (string text, out WarningSuppressionWriter.FileOutputKind fileOutputKind)
+ {
+ switch (text.ToLowerInvariant ()) {
+ case "cs":
+ fileOutputKind = WarningSuppressionWriter.FileOutputKind.CSharp;
+ return true;
+
+ case "xml":
+ fileOutputKind = WarningSuppressionWriter.FileOutputKind.Xml;
+ return true;
+
+ default:
+ fileOutputKind = WarningSuppressionWriter.FileOutputKind.CSharp;
+ return false;
+ }
+ }
+
+ bool GetBoolParam (string token, Action action)
+ {
+ if (arguments.Count == 0) {
+ action (true);
+ return true;
+ }
+
+ var arg = arguments.Peek ();
+ if (bool.TryParse (arg.ToLowerInvariant (), out bool value)) {
+ arguments.Dequeue ();
+ action (value);
+ return true;
+ }
+
+ if (arg.StartsWith ("-") || arg.StartsWith ("/")) {
+ action (true);
+ return true;
+ }
+
+ Context.LogError (null, DiagnosticId.InvalidArgumentForTokenOption, token);
+ return false;
+ }
+
+ bool GetStringParam (string token, [NotNullWhen (true)] out string? value)
+ {
+ value = null;
+ if (arguments.Count < 1) {
+ ErrorMissingArgument (token);
+ return false;
+ }
+
+ var arg = arguments.Dequeue ();
+ if (!string.IsNullOrEmpty (arg)) {
+ value = arg;
+ return true;
+ }
+
+ ErrorMissingArgument (token);
+ return false;
+ }
+
+ string? GetNextStringValue ()
+ {
+ if (arguments.Count < 1)
+ return null;
+
+ var arg = arguments.Peek ();
+ if (arg.StartsWith ("-") || arg.StartsWith ("/"))
+ return null;
+
+ arguments.Dequeue ();
+ return arg;
+ }
+
+ protected virtual LinkContext GetDefaultContext (Pipeline pipeline, ILogger? logger)
+ {
+ return new LinkContext (pipeline, logger ?? new ConsoleLogger (), "output") {
+ TrimAction = AssemblyAction.Link,
+ DefaultAction = AssemblyAction.Link,
+ };
+ }
+
+ protected virtual List CreateDefaultResolvers ()
+ {
+ return new List ();
+ }
+
+ static bool IsValidAssemblyName (string value)
+ {
+ return !string.IsNullOrEmpty (value);
+ }
+
+ static void Usage ()
+ {
+ Console.WriteLine (_linker);
+
+ Console.WriteLine ($"illink [options] {resolvers}");
+ Console.WriteLine (" -a FILE [MODE] Assembly file used as root assembly with optional MODE value to alter default root mode");
+ Console.WriteLine (" Mode can be one of the following values");
+ Console.WriteLine (" all: Keep all members in root assembly");
+ Console.WriteLine (" default: Use entry point for applications and all members for libraries");
+ Console.WriteLine (" entrypoint: Use assembly entry point as only root in the assembly");
+ Console.WriteLine (" library: All assembly members and data needed for secondary trimming are retained");
+ Console.WriteLine (" visible: Keep all members and types visible outside of the assembly");
+ Console.WriteLine (" -x FILE XML descriptor file with members to be kept");
+
+ Console.WriteLine ();
+ Console.WriteLine ("Options");
+ Console.WriteLine (" -d PATH Specify additional directory to search in for assembly references");
+ Console.WriteLine (" -reference FILE Specify additional file location used to resolve assembly references");
+ Console.WriteLine (" -b Update debug symbols for all modified files. Defaults to false");
+ Console.WriteLine (" -out PATH Specify the output directory. Defaults to 'output'");
+ Console.WriteLine (" -h Lists all {0} options", _linker);
+ Console.WriteLine (" @FILE Read response file for more options");
+
+ Console.WriteLine ();
+ Console.WriteLine ("Actions");
+ Console.WriteLine (" --trim-mode ACTION Sets action for assemblies annotated with IsTrimmable attribute. Defaults to 'link'");
+ Console.WriteLine (" copy: Analyze whole assembly and save it to the output");
+ Console.WriteLine (" copyused: Same as copy but only for assemblies which are needed");
+ Console.WriteLine (" link: Remove any unused IL or metadata and optimizes the assembly");
+ Console.WriteLine (" skip: Do not process the assembly");
+ Console.WriteLine (" addbypassngen: Add BypassNGenAttribute to unused methods");
+ Console.WriteLine (" addbypassngenused: Same as addbypassngen but unused assemblies are removed");
+ Console.WriteLine (" --action ACTION Sets action for assemblies that have no IsTrimmable attribute. Defaults to 'link'");
+ Console.WriteLine (" --action ACTION ASM Overrides the default action for specific assembly name");
+
+ Console.WriteLine ();
+ Console.WriteLine ("Advanced Options");
+ Console.WriteLine (" --about About the {0}", _linker);
+ Console.WriteLine (" --custom-step CFG Add a custom step to the existing pipeline");
+ Console.WriteLine (" Step can use one of following configurations");
+ Console.WriteLine (" TYPE,PATH_TO_ASSEMBLY: Add user defined type as last step to the pipeline");
+ Console.WriteLine (" -NAME:TYPE,PATH_TO_ASSEMBLY: Inserts step type before existing step with name");
+ Console.WriteLine (" +NAME:TYPE,PATH_TO_ASSEMBLY: Add step type after existing step");
+ Console.WriteLine (" --custom-data KEY=VALUE Populates context data set with user specified key-value pair");
+ Console.WriteLine (" --deterministic Produce a deterministic output for modified assemblies");
+ Console.WriteLine (" --ignore-descriptors Skips reading embedded descriptors (short -z). Defaults to false");
+ Console.WriteLine (" --skip-unresolved Ignore unresolved types, methods, and assemblies. Defaults to false");
+ Console.WriteLine (" --output-pinvokes PATH Output a JSON file with all modules and entry points of the P/Invokes found");
+ Console.WriteLine (" --verbose Log messages indicating progress and warnings");
+ Console.WriteLine (" --nowarn WARN Disable specific warning messages");
+ Console.WriteLine (" --warn VERSION Only print out warnings with version <= VERSION. Defaults to '9999'");
+ Console.WriteLine (" VERSION is an integer in the range 0-9999.");
+ Console.WriteLine (" --warnaserror[+|-] Report all warnings as errors");
+ Console.WriteLine (" --warnaserror[+|-] WARN Report specific warnings as errors");
+ Console.WriteLine (" --singlewarn[+|-] Show at most one analysis warning per assembly");
+ Console.WriteLine (" --singlewarn[+|-] ASM Show at most one analysis warning for a specific assembly");
+ Console.WriteLine (" --version Print the version number of the {0}", _linker);
+
+ Console.WriteLine ();
+ Console.WriteLine ("Trimming");
+ Console.WriteLine (" --disable-opt NAME [ASM] Disable one of the default optimizations globaly or for a specific assembly name");
+ Console.WriteLine (" beforefieldinit: Unused static fields are removed if there is no static ctor");
+ Console.WriteLine (" ipconstprop: Interprocedural constant propagation on return values");
+ Console.WriteLine (" overrideremoval: Overrides of virtual methods on types that are never instantiated are removed");
+ Console.WriteLine (" unreachablebodies: Instance methods that are marked but not executed are converted to throws");
+ Console.WriteLine (" unusedinterfaces: Removes interface types from declaration when not used");
+ Console.WriteLine (" unusedtypechecks: Inlines never successful type checks");
+ Console.WriteLine (" --enable-opt NAME [ASM] Enable one of the additional optimizations globaly or for a specific assembly name");
+ Console.WriteLine (" sealer: Any method or type which does not have override is marked as sealed");
+ Console.WriteLine (" --explicit-reflection Adds to members never used through reflection DisablePrivateReflection attribute. Defaults to false");
+ Console.WriteLine (" --feature FEATURE VALUE Apply any optimizations defined when this feature setting is a constant known at link time");
+ Console.WriteLine (" --keep-compilers-resources Keep assembly resources used for F# compilation resources. Defaults to false");
+ Console.WriteLine (" --keep-dep-attributes Keep attributes used for manual dependency tracking. Defaults to false");
+ Console.WriteLine (" --keep-metadata NAME Keep metadata which would otherwise be removed if not used");
+ Console.WriteLine (" all: Metadata for any member are all kept");
+ Console.WriteLine (" parametername: All parameter names are kept");
+ Console.WriteLine (" --new-mvid Generate a new guid for each linked assembly (short -g). Defaults to true");
+ Console.WriteLine (" --strip-descriptors Remove XML descriptor resources for linked assemblies. Defaults to true");
+ Console.WriteLine (" --strip-security Remove metadata and code related to Code Access Security. Defaults to true");
+ Console.WriteLine (" --substitutions FILE Configuration file with field or methods substitution rules");
+ Console.WriteLine (" --ignore-substitutions Skips reading embedded substitutions. Defaults to false");
+ Console.WriteLine (" --strip-substitutions Remove XML substitution resources for linked assemblies. Defaults to true");
+ Console.WriteLine (" --used-attrs-only Attribute usage is removed if the attribute type is not used. Defaults to false");
+ Console.WriteLine (" --link-attributes FILE Supplementary custom attribute definitions for attributes controlling the linker behavior.");
+ Console.WriteLine (" --ignore-link-attributes Skips reading embedded attributes. Defaults to false");
+ Console.WriteLine (" --strip-link-attributes Remove XML link attributes resources for linked assemblies. Defaults to true");
+
+ Console.WriteLine ();
+ Console.WriteLine ("Analyzer");
+ Console.WriteLine (" --dependencies-file FILE Specify the dependencies output. Defaults to 'output/linker-dependencies.xml'");
+ Console.WriteLine (" if 'xml' is file format, 'output/linker-dependencies.dgml if 'dgml' is file format");
+ Console.WriteLine (" --dump-dependencies Dump dependencies for the linker analyzer tool");
+ Console.WriteLine (" --dependencies-file-format FORMAT Specify output file type. Defaults to 'xml'");
+ Console.WriteLine (" xml: outputs an .xml file");
+ Console.WriteLine (" dgml: outputs a .dgml file");
+ Console.WriteLine (" --reduced-tracing Reduces dependency output related to assemblies that will not be modified");
+ Console.WriteLine ("");
+ }
+
+ static void Version ()
+ {
+ Console.WriteLine ("{0} Version {1}",
+ _linker,
+ System.Reflection.Assembly.GetExecutingAssembly ().GetName ().Version);
+ }
+
+ static void About ()
+ {
+ Console.WriteLine ("For more information, visit the project Web site");
+ Console.WriteLine (" https://github.com/dotnet/linker");
+ }
+
+ static Pipeline GetStandardPipeline ()
+ {
+ Pipeline p = new Pipeline ();
+ p.AppendStep (new ProcessReferencesStep ());
+ p.AppendStep (new MarkStep ());
+ p.AppendStep (new RemoveResourcesStep ());
+ p.AppendStep (new ValidateVirtualMethodAnnotationsStep ());
+ p.AppendStep (new ProcessWarningsStep ());
+ p.AppendStep (new OutputWarningSuppressions ());
+ p.AppendStep (new SweepStep ());
+ p.AppendStep (new CheckSuppressionsDispatcher ());
+ p.AppendStep (new CodeRewriterStep ());
+ p.AppendStep (new CleanStep ());
+ p.AppendStep (new RegenerateGuidStep ());
+ p.AppendStep (new OutputStep ());
+ return p;
+ }
+
+ public void Dispose ()
+ {
+ context?.Dispose ();
+ }
+ }
+}
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.BaseProvidesInterfaceEdgeCaseTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.BaseProvidesInterfaceEdgeCaseTests.g.cs
new file mode 100644
index 00000000000..5aaf8776a26
--- /dev/null
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.BaseProvidesInterfaceEdgeCaseTests.g.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace ILLink.RoslynAnalyzer.Tests.Inheritance.Interfaces
+{
+ public sealed partial class BaseProvidesInterfaceEdgeCaseTests : LinkerTestBase
+ {
+
+ protected override string TestSuiteName => "Inheritance.Interfaces.BaseProvidesInterfaceEdgeCase";
+
+ [Fact]
+ public Task BaseProvidesInterfaceMethodCircularReference ()
+ {
+ return RunTest (allowMissingWarnings: true);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/test/ILLink.Tasks.Tests/Mock.cs b/src/tools/illink/test/ILLink.Tasks.Tests/Mock.cs
new file mode 100644
index 00000000000..979d62b1448
--- /dev/null
+++ b/src/tools/illink/test/ILLink.Tasks.Tests/Mock.cs
@@ -0,0 +1,228 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Build.Framework;
+using Mono.Linker;
+using Mono.Linker.Steps;
+
+namespace ILLink.Tasks.Tests
+{
+ public class MockTask : ILLink
+ {
+
+ public List<(MessageImportance Importance, string Line)> Messages { get; } = new List<(MessageImportance Importance, string Line)> ();
+
+ public MockTask ()
+ {
+ // Ensure that [Required] members are non-null
+ AssemblyPaths = Array.Empty ();
+ RootAssemblyNames = Array.Empty ();
+ ILLinkPath = Path.Combine (Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location), "illink.dll");
+ }
+
+ public MockDriver CreateDriver ()
+ {
+ using (var responseFileText = new StringReader (GenerateResponseFileCommands ())) {
+ var arguments = new Queue ();
+ Driver.ParseResponseFile (responseFileText, arguments);
+ return new MockDriver (arguments);
+ }
+ }
+
+ public static string[] OptimizationNames {
+ get {
+ var field = typeof (ILLink).GetField ("_optimizationNames", BindingFlags.NonPublic | BindingFlags.Static);
+ return (string[]) field.GetValue (null);
+ }
+ }
+
+ public void SetOptimization (string optimization, bool enabled)
+ {
+ var property = typeof (ILLink).GetProperty (optimization);
+ property.GetSetMethod ().Invoke (this, new object[] { enabled });
+ }
+
+ static readonly string[] nonOptimizationBooleanProperties = new string[] {
+ "DumpDependencies",
+ "RemoveSymbols",
+ "TreatWarningsAsErrors",
+ "SingleWarn"
+ };
+
+ public static IEnumerable GetOptimizationPropertyNames ()
+ {
+ foreach (var property in typeof (ILLink).GetProperties (BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)) {
+ if (property.PropertyType != typeof (bool))
+ continue;
+ if (nonOptimizationBooleanProperties.Contains (property.Name))
+ continue;
+ yield return property.Name;
+ }
+ }
+
+ protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance) => Messages.Add ((messageImportance, singleLine));
+ }
+
+ public class MockBuildEngine : IBuildEngine
+ {
+ public void LogErrorEvent (BuildErrorEventArgs e) { }
+ public void LogWarningEvent (BuildWarningEventArgs e) { }
+ public void LogMessageEvent (BuildMessageEventArgs e) { }
+ public void LogCustomEvent (CustomBuildEventArgs e) { }
+ public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) => false;
+ public bool ContinueOnError => false;
+ public int LineNumberOfTaskNode => 0;
+ public int ColumnNumberOfTaskNode => 0;
+ public string ProjectFileOfTaskNode => null;
+ }
+
+ public class MockDriver : Driver
+ {
+ public class CustomLogger : Mono.Linker.ILogger
+ {
+ public List Messages = new List ();
+
+ public void LogMessage (MessageContainer message)
+ {
+ Messages.Add (message);
+ }
+ }
+
+ public MockDriver (Queue arguments) : base (arguments)
+ {
+ // Always set up the context early on.
+ Logger = new CustomLogger ();
+ SetupContext (Logger);
+ }
+
+ public new LinkContext Context => base.Context;
+
+ public CustomLogger Logger { get; private set; }
+
+ public IEnumerable GetRootAssemblies ()
+ {
+ foreach (var step in Context.Pipeline.GetSteps ()) {
+ if (!(step is RootAssemblyInput))
+ continue;
+
+ var assemblyName = (string) typeof (RootAssemblyInput).GetField ("fileName", BindingFlags.NonPublic | BindingFlags.Instance).GetValue (step);
+ if (assemblyName == null)
+ continue;
+
+ yield return assemblyName;
+ }
+ }
+
+ public IEnumerable GetRootDescriptors ()
+ {
+ foreach (var step in Context.Pipeline.GetSteps ()) {
+ if (!(step is ResolveFromXmlStep))
+ continue;
+
+ var descriptor = (string) typeof (ResolveFromXmlStep).GetField ("_xmlDocumentLocation", BindingFlags.NonPublic | BindingFlags.Instance).GetValue (step);
+
+ yield return descriptor;
+ }
+ }
+
+ public IEnumerable GetReferenceAssemblies ()
+ {
+ return (IEnumerable) typeof (AssemblyResolver).GetField ("_references", BindingFlags.NonPublic | BindingFlags.Instance).GetValue (Context.Resolver);
+ }
+
+ protected override void AddResolveFromXmlStep (Pipeline pipeline, string file)
+ {
+ // Don't try to load an xml file - just pretend it exists.
+ pipeline.PrependStep (new ResolveFromXmlStep (documentStream: null, file));
+ }
+
+ protected override void AddXmlDependencyRecorder (LinkContext context, string file)
+ {
+ // Don't try to open the output file for writing - just pretend it exists.
+ Context.Tracer.AddRecorder (MockXmlDependencyRecorder.Singleton);
+ }
+
+ protected override void AddDgmlDependencyRecorder (LinkContext context, string file)
+ {
+ // Don't try to open the output file for writing - just pretend it exists.
+ Context.Tracer.AddRecorder (MockDgmlDependencyRecorder.Singleton);
+ }
+
+ public IEnumerable GetDependencyRecorders ()
+ {
+ return (IEnumerable) typeof (Tracer).GetField ("recorders", BindingFlags.NonPublic | BindingFlags.Instance).GetValue (Context.Tracer);
+ }
+
+ public new bool GetOptimizationName (string optimization, out CodeOptimizations codeOptimizations)
+ {
+ return base.GetOptimizationName (optimization, out codeOptimizations);
+ }
+
+ public CodeOptimizations GetDefaultOptimizations ()
+ {
+ return Context.Optimizations.Global;
+ }
+
+ public Dictionary GetCustomData ()
+ {
+ var field = typeof (LinkContext).GetField ("_parameters", BindingFlags.NonPublic | BindingFlags.Instance);
+ return (Dictionary) field.GetValue (Context);
+ }
+
+ protected override List CreateDefaultResolvers ()
+ {
+ return new List () {
+ new RootAssemblyInput (null, AssemblyRootMode.EntryPoint)
+ };
+ }
+ }
+
+ public class MockXmlDependencyRecorder : IDependencyRecorder
+ {
+ public static MockXmlDependencyRecorder Singleton { get; } = new MockXmlDependencyRecorder ();
+ public void RecordDependency (object source, object arget, bool marked) { }
+ public void RecordDependency (object target, in DependencyInfo reason, bool marked) { }
+ public void FinishRecording () { }
+ }
+
+ public class MockDgmlDependencyRecorder : IDependencyRecorder
+ {
+ public static MockXmlDependencyRecorder Singleton { get; } = new MockXmlDependencyRecorder ();
+ public void RecordDependency (object source, object arget, bool marked) { }
+ public void RecordDependency (object target, in DependencyInfo reason, bool marked) { }
+ public void FinishRecording () { }
+ }
+
+ public class MockCustomStep : IStep
+ {
+ public void Process (LinkContext context) { }
+ }
+
+ public class MockCustomStep2 : MockCustomStep { }
+
+ public class MockCustomStep3 : MockCustomStep { }
+
+ public class MockCustomStep4 : MockCustomStep { }
+
+ public class MockCustomStep5 : MockCustomStep { }
+
+ public class MockCustomStep6 : MockCustomStep { }
+
+ public class MockMarkHandler : IMarkHandler
+ {
+ public void Initialize (LinkContext context, MarkContext markContext) { }
+ }
+
+ public class MockMarkHandler2 : MockMarkHandler { }
+
+ public class MockMarkHandler3 : MockMarkHandler { }
+
+ public class MockMarkHandler4 : MockMarkHandler { }
+
+}
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/StaticInterfaceMethodsInPreservedScope.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/StaticInterfaceMethodsInPreservedScope.cs
new file mode 100644
index 00000000000..362413c3f97
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/StaticInterfaceMethodsInPreservedScope.cs
@@ -0,0 +1,74 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Helpers;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+using Mono.Linker.Tests.Cases.Inheritance.Interfaces.StaticInterfaceMethods.Dependencies;
+
+namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.StaticInterfaceMethods
+{
+ [SetupCompileBefore ("library.dll", new[] { "Dependencies/Library.cs" })]
+ [SetupLinkerAction ("skip", "library")]
+ public static class StaticInterfaceMethodsInPreservedScope
+ {
+ [Kept]
+ public static void Main ()
+ {
+ var x = typeof (VirtualInterfaceMethods);
+ x = typeof (AbstractInterfaceMethods);
+ x = typeof (IStaticInterfaceWithDefaultImpls);
+ x = typeof (IStaticAbstractMethods);
+ }
+
+ [Kept]
+ [KeptInterface (typeof (IStaticInterfaceWithDefaultImpls))]
+ public class VirtualInterfaceMethods : IStaticInterfaceWithDefaultImpls
+ {
+ [Kept]
+ static int IStaticInterfaceWithDefaultImpls.Property {
+ [Kept]
+ [KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))]
+ get => 1;
+ [Kept]
+ [KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))]
+ set => _ = value;
+ }
+
+ [Kept]
+ [KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))]
+ static int IStaticInterfaceWithDefaultImpls.Method () => 1;
+
+ // There is a default implementation and the type isn't instantiated, so we don't need this
+ int IStaticInterfaceWithDefaultImpls.InstanceMethod () => 0;
+ }
+
+ [Kept]
+ [KeptInterface (typeof (IStaticAbstractMethods))]
+ public class AbstractInterfaceMethods : IStaticAbstractMethods
+ {
+ [Kept]
+ static int IStaticAbstractMethods.Property {
+ [Kept]
+ [KeptOverride (typeof (IStaticAbstractMethods))]
+ get => 1; [Kept]
+ [KeptOverride (typeof (IStaticAbstractMethods))]
+ set => _ = value;
+ }
+
+ [Kept]
+ [KeptOverride (typeof (IStaticAbstractMethods))]
+ static int IStaticAbstractMethods.Method () => 1;
+
+ [Kept]
+ [KeptOverride (typeof (IStaticAbstractMethods))]
+ int IStaticAbstractMethods.InstanceMethod () => 0;
+ }
+ }
+}
+
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/StaticVirtualInterfaceMethodsInPreservedScope.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/StaticVirtualInterfaceMethodsInPreservedScope.cs
new file mode 100644
index 00000000000..26a5a2cc066
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/StaticVirtualInterfaceMethodsInPreservedScope.cs
@@ -0,0 +1,98 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Helpers;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+using Mono.Linker.Tests.Cases.Inheritance.Interfaces.StaticInterfaceMethods.Dependencies;
+
+namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.StaticInterfaceMethods
+{
+ [SetupCompileBefore ("library.dll", new[] { "Dependencies/Library.cs" })]
+ [SetupLinkerAction ("skip", "library")]
+ public static class StaticVirtualInterfaceMethodsInPreservedScope
+ {
+ [Kept]
+ public static void Main ()
+ {
+ NotRelevantToVariantCasting.Keep ();
+ var t = typeof (RelevantToVariantCasting);
+ MarkInterfaceMethods ();
+ var x = new InstantiatedClass ();
+ }
+
+ [Kept]
+ static void MarkInterfaceMethods () where T : IStaticInterfaceWithDefaultImpls
+ {
+ T.Property = T.Property + 1;
+ T.Method ();
+ CallInstanceMethod (null);
+
+ [Kept]
+ void CallInstanceMethod (IStaticInterfaceWithDefaultImpls x)
+ {
+ x.InstanceMethod ();
+ }
+ }
+
+ [Kept]
+ [KeptInterface (typeof (IStaticInterfaceWithDefaultImpls))]
+ public class RelevantToVariantCasting : IStaticInterfaceWithDefaultImpls
+ {
+ [Kept]
+ static int IStaticInterfaceWithDefaultImpls.Property { [Kept][KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))] get => 1; [Kept][KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))] set => _ = value; }
+ [Kept]
+ [KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))]
+ static int IStaticInterfaceWithDefaultImpls.Method () => 1;
+ int IStaticInterfaceWithDefaultImpls.InstanceMethod () => 0;
+ }
+
+ [Kept]
+ [KeptInterface (typeof (IStaticInterfaceWithDefaultImpls))]
+ public class UsedAsTypeArgument : IStaticInterfaceWithDefaultImpls
+ {
+ [Kept]
+ static int IStaticInterfaceWithDefaultImpls.Property { [Kept][KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))] get => 1; [Kept][KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))] set => _ = value; }
+ [Kept]
+ [KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))]
+ static int IStaticInterfaceWithDefaultImpls.Method () => 1;
+ int IStaticInterfaceWithDefaultImpls.InstanceMethod () => 0;
+ }
+
+ [Kept]
+ public class NotRelevantToVariantCasting : IStaticInterfaceWithDefaultImpls
+ {
+ [Kept]
+ public static void Keep () { }
+ static int IStaticInterfaceWithDefaultImpls.Property { get => 1; set => _ = value; }
+ static int IStaticInterfaceWithDefaultImpls.Method () => 1;
+ int IStaticInterfaceWithDefaultImpls.InstanceMethod () => 0;
+ }
+ [Kept]
+ [KeptMember (".ctor()")]
+ [KeptInterface (typeof (IStaticInterfaceWithDefaultImpls))]
+ public class InstantiatedClass : IStaticInterfaceWithDefaultImpls
+ {
+ [Kept]
+ static int IStaticInterfaceWithDefaultImpls.Property {
+ [Kept]
+ [KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))]
+ get => 1;
+ [Kept]
+ [KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))]
+ set => _ = value;
+ }
+ [Kept]
+ [KeptOverride (typeof (IStaticInterfaceWithDefaultImpls))]
+ static int IStaticInterfaceWithDefaultImpls.Method () => 1;
+ [Kept]
+ int IStaticInterfaceWithDefaultImpls.InstanceMethod () => 0;
+ }
+ }
+}
+
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Libraries/DefaultLibraryLinkBehavior.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Libraries/DefaultLibraryLinkBehavior.cs
new file mode 100644
index 00000000000..fd0c5578162
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Libraries/DefaultLibraryLinkBehavior.cs
@@ -0,0 +1,29 @@
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.Libraries
+{
+ [SetupCompileAsLibrary]
+ [SetupLinkerArgument ("-a", "test.dll")]
+ [Kept]
+ [KeptMember (".ctor()")]
+ public class DefaultLibraryLinkBehavior
+ {
+ // Kept because by default libraries their action set to copy
+ [Kept]
+ public static void Main ()
+ {
+ // Main is needed for the test collector to find and treat as a test
+ }
+
+ [Kept]
+ public void UnusedPublicMethod ()
+ {
+ }
+
+ [Kept]
+ private void UnusedPrivateMethod ()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/LinkerArgumentBuilder.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/LinkerArgumentBuilder.cs
new file mode 100644
index 00000000000..ae0673e9903
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/LinkerArgumentBuilder.cs
@@ -0,0 +1,251 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using Mono.Linker.Tests.Extensions;
+
+namespace Mono.Linker.Tests.TestCasesRunner
+{
+ public class LinkerArgumentBuilder
+ {
+ private readonly List _arguments = new List ();
+ private readonly TestCaseMetadataProvider _metadataProvider;
+
+ public LinkerArgumentBuilder (TestCaseMetadataProvider metadataProvider)
+ {
+ _metadataProvider = metadataProvider;
+ }
+
+ public virtual void AddSearchDirectory (NPath directory)
+ {
+ Append ("-d");
+ Append (directory.ToString ());
+ }
+
+ public virtual void AddReference (NPath path)
+ {
+ Append ("-reference");
+ Append (path.ToString ());
+ }
+
+ public virtual void AddOutputDirectory (NPath directory)
+ {
+ Append ("-o");
+ Append (directory.ToString ());
+ }
+
+ public virtual void AddLinkXmlFile (string file)
+ {
+ Append ("-x");
+ Append (file);
+ }
+
+ public virtual void AddResponseFile (NPath path)
+ {
+ Append ($"@{path}");
+ }
+
+ public virtual void AddTrimMode (string value)
+ {
+ Append ("--trim-mode");
+ Append (value);
+ }
+
+ public virtual void AddDefaultAction (string value)
+ {
+ Append ("--action");
+ Append (value);
+ }
+
+ public virtual void LinkFromAssembly (string fileName)
+ {
+ Append ("-a");
+ Append (fileName);
+ Append ("entrypoint");
+ }
+
+ public virtual void LinkFromPublicAndFamily (string fileName)
+ {
+#if NETCOREAPP
+ Append ("-a");
+ Append (fileName);
+ Append ("visible");
+#else
+ Append ("-r");
+ Append (fileName);
+#endif
+ }
+
+ public virtual void IgnoreDescriptors (bool value)
+ {
+ Append ("--ignore-descriptors");
+ Append (value ? "true" : "false");
+ }
+
+ public virtual void IgnoreSubstitutions (bool value)
+ {
+ Append ("--ignore-substitutions");
+ Append (value ? "true" : "false");
+ }
+
+ public virtual void IgnoreLinkAttributes (bool value)
+ {
+ Append ("--ignore-link-attributes");
+ Append (value ? "true" : "false");
+ }
+
+ public virtual void AddIl8n (string value)
+ {
+ Append ("-l");
+ Append (value);
+ }
+
+ public virtual void AddLinkSymbols (string value)
+ {
+ Append ("-b");
+ Append (value);
+ }
+
+ public virtual void AddKeepDebugMembers (string value)
+ {
+ Append ("-v");
+ Append (value);
+ }
+
+ public virtual void AddAssemblyAction (string action, string assembly)
+ {
+ Append ("--action");
+ Append (action);
+ Append (assembly);
+ }
+
+ public virtual void AddSkipUnresolved (bool skipUnresolved)
+ {
+ if (skipUnresolved) {
+ Append ("--skip-unresolved");
+ Append ("true");
+ }
+ }
+
+ public virtual void AddStripDescriptors (bool stripDescriptors)
+ {
+ if (!stripDescriptors) {
+ Append ("--strip-descriptors");
+ Append ("false");
+ }
+ }
+
+ public virtual void AddStripSubstitutions (bool stripSubstitutions)
+ {
+ if (!stripSubstitutions) {
+ Append ("--strip-substitutions");
+ Append ("false");
+ }
+ }
+
+ public virtual void AddStripLinkAttributes (bool stripLinkAttributes)
+ {
+ if (!stripLinkAttributes) {
+ Append ("--strip-link-attributes");
+ Append ("false");
+ }
+ }
+
+ public virtual void AddSubstitutions (string file)
+ {
+ Append ("--substitutions");
+ Append (file);
+ }
+
+ public virtual void AddLinkAttributes (string file)
+ {
+ Append ("--link-attributes");
+ Append (file);
+ }
+
+ public string[] ToArgs ()
+ {
+ return _arguments.ToArray ();
+ }
+
+ protected void Append (string arg)
+ {
+ _arguments.Add (arg);
+ }
+
+ public virtual void AddAdditionalArgument (string flag, string[] values)
+ {
+ Append (flag);
+ if (values != null) {
+ foreach (var val in values)
+ Append (val);
+ }
+ }
+
+ public virtual void ProcessTestInputAssembly (NPath inputAssemblyPath)
+ {
+ if (_metadataProvider.LinkPublicAndFamily ())
+ LinkFromPublicAndFamily (inputAssemblyPath.ToString ());
+ else
+ LinkFromAssembly (inputAssemblyPath.ToString ());
+ }
+
+ public virtual void ProcessOptions (TestCaseLinkerOptions options)
+ {
+ if (options.TrimMode != null)
+ AddTrimMode (options.TrimMode);
+
+ if (options.DefaultAssembliesAction != null)
+ AddDefaultAction (options.DefaultAssembliesAction);
+
+ if (options.AssembliesAction != null) {
+ foreach (var entry in options.AssembliesAction)
+ AddAssemblyAction (entry.Key, entry.Value);
+ }
+
+ // Honoring descriptors causes a ton of stuff to be preserved. That's good for normal use cases, but for
+ // our test cases that pollutes the results
+ IgnoreDescriptors (options.IgnoreDescriptors);
+
+ IgnoreSubstitutions (options.IgnoreSubstitutions);
+
+ IgnoreLinkAttributes (options.IgnoreLinkAttributes);
+
+#if !NETCOREAPP
+ if (!string.IsNullOrEmpty (options.Il8n))
+ AddIl8n (options.Il8n);
+#endif
+
+ if (!string.IsNullOrEmpty (options.LinkSymbols))
+ AddLinkSymbols (options.LinkSymbols);
+
+ if (!string.IsNullOrEmpty (options.KeepDebugMembers))
+ AddKeepDebugMembers (options.KeepDebugMembers);
+
+ AddSkipUnresolved (options.SkipUnresolved);
+
+ AddStripDescriptors (options.StripDescriptors);
+
+ AddStripSubstitutions (options.StripSubstitutions);
+
+ AddStripLinkAttributes (options.StripLinkAttributes);
+
+ foreach (var descriptor in options.Descriptors)
+ AddLinkXmlFile (descriptor);
+
+ foreach (var substitutions in options.Substitutions)
+ AddSubstitutions (substitutions);
+
+ foreach (var attributeDefinition in options.LinkAttributes)
+ AddLinkAttributes (attributeDefinition);
+
+ // A list of expensive optimizations which should not run by default
+ AddAdditionalArgument ("--disable-opt", new[] { "ipconstprop" });
+
+ // Unity uses different argument format and needs to be able to translate to their format. In order to make that easier
+ // we keep the information in flag + values format for as long as we can so that this information doesn't have to be parsed out of a single string
+ foreach (var additionalArgument in options.AdditionalArguments)
+ AddAdditionalArgument (additionalArgument.Key, additionalArgument.Value);
+ }
+ }
+}
\ No newline at end of file