diff --git a/src/ILLink.Tasks/LinkTask.cs b/src/ILLink.Tasks/LinkTask.cs index 6c9b15a65ade..6be3a58c131e 100644 --- a/src/ILLink.Tasks/LinkTask.cs +++ b/src/ILLink.Tasks/LinkTask.cs @@ -158,6 +158,13 @@ protected override string GenerateResponseFileCommands () args.Append (action); args.Append (" ").AppendLine (Quote (assemblyName)); } + + string actionflag = assembly.GetMetadata ("actionflag"); + if ((actionflag != null) && (actionflag.Length > 0)) { + args.Append ("-f "); + args.Append (actionflag); + args.Append (" ").AppendLine (Quote (assemblyName)); + } } if (ReferenceAssemblyPaths != null) { diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 44d2e9e4cad6..ce7a3bb0303a 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -1699,6 +1699,10 @@ void ApplyPreserveInfo (TypeDefinition type) { ApplyPreserveMethods (type); + if (_context.HasActionFlag(type.Module.Assembly, AssemblyActionFlag.TypeGranularity)) { + Annotations.SetPreserve (type, TypePreserve.All); + } + if (!Annotations.TryGetPreserve (type, out TypePreserve preserve)) return; diff --git a/src/linker/Linker/AssemblyActionFlag.cs b/src/linker/Linker/AssemblyActionFlag.cs new file mode 100644 index 000000000000..7693f9b64f97 --- /dev/null +++ b/src/linker/Linker/AssemblyActionFlag.cs @@ -0,0 +1,9 @@ +using System; + +namespace Mono.Linker { + [Flags] + public enum AssemblyActionFlag : int { + // If there is any reference to a type, preserve all members in that type + TypeGranularity = 1, + } +} diff --git a/src/linker/Linker/Driver.cs b/src/linker/Linker/Driver.cs index e1cf45a38372..92ed04ecb786 100644 --- a/src/linker/Linker/Driver.cs +++ b/src/linker/Linker/Driver.cs @@ -309,6 +309,13 @@ public void Run (ILogger customLogger = null) AssemblyAction action = ParseAssemblyAction (GetParam ()); context.Actions [GetParam ()] = action; break; + case 'f': + AssemblyActionFlag newFlag = ParseAssemblyActionFlag (GetParam ()); + string flagTargetName = GetParam (); + context.ActionFlags [flagTargetName] = context.ActionFlags.TryGetValue (flagTargetName, out AssemblyActionFlag existingFlag) + ? existingFlag | newFlag + : newFlag; + break; case 't': context.KeepTypeForwarderOnlyAssemblies = true; break; @@ -535,6 +542,11 @@ AssemblyAction ParseAssemblyAction (string s) return assemblyAction; } + AssemblyActionFlag ParseAssemblyActionFlag (string s) + { + return (AssemblyActionFlag)Enum.Parse (typeof (AssemblyActionFlag), s, true); + } + string GetParam () { if (_queue.Count == 0) @@ -591,6 +603,8 @@ static void Usage (string msg) Console.WriteLine (" addbypassngenused: Same as addbypassngen but unused assemblies are removed"); Console.WriteLine (" -u Action on the user assemblies. Defaults to 'link'"); Console.WriteLine (" -p Overrides the default action for an assembly"); + Console.WriteLine (" -f Adds a linker action flag for an assembly"); + Console.WriteLine (" typegranularity: If there is any reference to a type, preserve the whole type"); Console.WriteLine (); Console.WriteLine ("Advanced"); diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index a94a42c4c331..da9e919cb37a 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -47,6 +47,7 @@ public class LinkContext : IDisposable { AssemblyAction _userAction; Dictionary _actions; string _outputDirectory; + readonly Dictionary _actionFlags; readonly Dictionary _parameters; bool _linkSymbols; bool _keepTypeForwarderOnlyAssemblies; @@ -123,6 +124,10 @@ public System.Collections.IDictionary Actions { get { return _actions; } } + public IDictionary ActionFlags { + get { return _actionFlags; } + } + public AssemblyResolver Resolver { get { return _resolver; } } @@ -178,6 +183,7 @@ public LinkContext (Pipeline pipeline, AssemblyResolver resolver, ReaderParamete _resolver = resolver; _resolver.Context = this; _actions = new Dictionary (); + _actionFlags = new Dictionary (); _parameters = new Dictionary (); _readerParameters = readerParameters; @@ -400,6 +406,12 @@ public string GetParameter (string key) return val; } + public bool HasActionFlag (AssemblyDefinition assembly, AssemblyActionFlag flag) + { + return ActionFlags.TryGetValue (assembly.Name.Name, out AssemblyActionFlag foundFlags) + && foundFlags.HasFlag (flag); + } + public void Dispose () { _resolver.Dispose (); diff --git a/test/Mono.Linker.Tests.Cases/TypeGranularity/UsedTypeKeepsAllMembersWithTypeGranularity.cs b/test/Mono.Linker.Tests.Cases/TypeGranularity/UsedTypeKeepsAllMembersWithTypeGranularity.cs new file mode 100644 index 000000000000..d6126ec2200f --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/TypeGranularity/UsedTypeKeepsAllMembersWithTypeGranularity.cs @@ -0,0 +1,34 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; +using System; + +namespace Mono.Linker.Tests.Cases.TypeGranularity +{ + [SetupLinkerArgument ("-f", "typegranularity", "test")] + static class UsedTypeKeepsAllMembersWithTypeGranularity { + public static void Main() { + GC.KeepAlive (typeof(ReferencedClass)); + } + + [KeptMember (".ctor()")] + class ReferencedClass { + [Kept] + [KeptBackingField] + public int Prop { [Kept] get; [Kept] set; } + + [Kept] + public void SomeMethod() { + Console.WriteLine (typeof (KeptNestedClass)); + } + + [KeptMember (".ctor()")] + class KeptNestedClass { } + + // Unkept because nested types are not members. Type-level granularity doesn't + // preserve nested classes unless there is a reference to them. + class UnkeptNestedClass { + public string unkeptField; + } + } + } +} diff --git a/test/Mono.Linker.Tests/TestCases/TestDatabase.cs b/test/Mono.Linker.Tests/TestCases/TestDatabase.cs index 726516d211d6..fdd96a681921 100644 --- a/test/Mono.Linker.Tests/TestCases/TestDatabase.cs +++ b/test/Mono.Linker.Tests/TestCases/TestDatabase.cs @@ -91,6 +91,11 @@ public static IEnumerable PreserveDependenciesTests () return NUnitCasesBySuiteName ("PreserveDependencies"); } + public static IEnumerable TypeGranularityTests () + { + return NUnitCasesBySuiteName ("TypeGranularity"); + } + public static IEnumerable LibrariesTests () { return NUnitCasesBySuiteName ("Libraries"); diff --git a/test/Mono.Linker.Tests/TestCases/TestSuites.cs b/test/Mono.Linker.Tests/TestCases/TestSuites.cs index c7d906c36c18..6ee7a7c058dc 100644 --- a/test/Mono.Linker.Tests/TestCases/TestSuites.cs +++ b/test/Mono.Linker.Tests/TestCases/TestSuites.cs @@ -103,6 +103,12 @@ public void PreserveDependenciesTests (TestCase testCase) Run (testCase); } + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.TypeGranularityTests))] + public void TypeGranularityTests (TestCase testCase) + { + Run (testCase); + } + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.SymbolsTests))] public void SymbolsTests (TestCase testCase) {