diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs
index 1a4fb259ded..f88b9481382 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs
@@ -518,7 +518,11 @@ internal static void ApplyPlaceholders (XDocument doc, string? placeholders, str
var eqIndex = entry.IndexOf ('=');
if (eqIndex > 0) {
var key = entry.Substring (0, eqIndex).Trim ();
- var value = entry.Substring (eqIndex + 1).Trim ();
+ // Normalize '\' to the platform directory separator to match the legacy pipeline,
+ // where the substituted manifest is re-encoded by aapt2 (which rewrites backslashes
+ // to '/' on Unix). The trimmable generator writes the merged manifest directly, so we
+ // apply the same normalization here to keep placeholder values byte-for-byte identical.
+ var value = entry.Substring (eqIndex + 1).Trim ().Replace ('\\', Path.DirectorySeparatorChar);
replacements ["${" + key + "}"] = value;
} else if (eqIndex < 0) {
// An entry without '=' is not a valid key=value pair. Mirror the legacy
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
index fb655661a24..e99f70ab8cc 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets
@@ -5,13 +5,22 @@
+
+ <_AndroidTrimmableTypemapTrimJavaCode Condition=" '$(_AndroidTrimmableTypemapTrimJavaCode)' == '' ">false
<_TrimmableRuntimeProviderJavaName Condition=" '$(_TrimmableRuntimeProviderJavaName)' == '' ">net.dot.jni.nativeaot.NativeAotRuntimeProvider
r8
d8
True
True
- true
+
+ true
<_UseTrimmableNativeAotProguardConfiguration Condition=" '$(_UseTrimmableNativeAotProguardConfiguration)' == '' ">true
+ <_TrimmableNativeAotProguardConfigurationInputsStamp>$(_AndroidStampDirectory)_GenerateTrimmableTypeMapProguardConfiguration.inputs
<_CompileToDalvikDependsOnTargets>$(_CompileToDalvikDependsOnTargets);_GenerateTrimmableTypeMapProguardConfiguration
@@ -67,27 +76,63 @@
DependsOnTargets="_GenerateTrimmableTypeMap" />
+ Condition=" '$(_AndroidTrimmableTypemapTrimJavaCode)' == 'true' and '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
<_TrimmableNativeAotRuntimeIdentifiers Remove="@(_TrimmableNativeAotRuntimeIdentifiers)" />
<_TrimmableNativeAotDgmlFiles Remove="@(_TrimmableNativeAotDgmlFiles)" />
+ <_TrimmableNativeAotCodegenDgmlFiles Remove="@(_TrimmableNativeAotCodegenDgmlFiles)" />
<_TrimmableNativeAotRuntimeIdentifiers Include="$(RuntimeIdentifier)" Condition=" '$(RuntimeIdentifier)' != '' " />
<_TrimmableNativeAotRuntimeIdentifiers Include="$(RuntimeIdentifiers)" Condition=" '$(RuntimeIdentifier)' == '' and '$(RuntimeIdentifiers)' != '' " />
-
- <_TrimmableNativeAotDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml" Condition=" '@(_TrimmableNativeAotRuntimeIdentifiers)' == '' " />
- <_TrimmableNativeAotDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml" Condition=" '$(RuntimeIdentifier)' != '' " />
- <_TrimmableNativeAotDgmlFiles Include="$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).scan.dgml.xml" Condition=" '$(RuntimeIdentifier)' == '' and '@(_TrimmableNativeAotRuntimeIdentifiers)' != '' " />
+
+ <_TrimmableNativeAotDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml"
+ Condition=" Exists('$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml') " />
+ <_TrimmableNativeAotDgmlFiles Include="$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).scan.dgml.xml"
+ Condition=" '@(_TrimmableNativeAotRuntimeIdentifiers)' != '' and Exists('$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).scan.dgml.xml') " />
+
+ <_TrimmableNativeAotCodegenDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).codegen.dgml.xml"
+ Condition=" Exists('$(NativeIntermediateOutputPath)$(TargetName).codegen.dgml.xml') " />
+ <_TrimmableNativeAotCodegenDgmlFiles Include="$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).codegen.dgml.xml"
+ Condition=" '@(_TrimmableNativeAotRuntimeIdentifiers)' != '' and Exists('$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).codegen.dgml.xml') " />
+ <_TrimmableNativeAotDgmlFiles Include="@(_TrimmableNativeAotCodegenDgmlFiles)"
+ Condition=" '@(_TrimmableNativeAotDgmlFiles->Count())' == '0' " />
+
+
+
+
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets
index 5090ba7fa6a..f6fb61e4b85 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets
@@ -375,6 +375,7 @@
OutputDirectory="$(IntermediateOutputPath)android"
TargetName="$(TargetName)"
Environments="@(_EnvironmentFiles)"
+ AdditionalProviderSources="@(_AdditionalProviderSources)"
EnableSGenConcurrent="$(AndroidEnableSGenConcurrent)" />
@@ -383,6 +384,15 @@
SkipUnchangedFiles="true"
Condition="Exists('$(_TypeMapBaseOutputDir)AndroidManifest.xml')" />
+
+
+
@@ -392,6 +402,8 @@
+
@@ -418,4 +430,41 @@
+
+
+
+
+ <_ResourceDesignerReferenceToRoot Include="@(ReferencePath)"
+ Condition=" '%(Filename)' == '_Microsoft.Android.Resource.Designer' " />
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs
index 9afb10b3404..436b06d7602 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs
@@ -70,18 +70,8 @@ void Generate (NativeCodeGenStateObject codeGenState)
Xamarin.Android.Tasks.AndroidRuntime.CoreCLR => true,
_ => false,
};
- string providerTemplateFile = isMonoVM ?
- "MonoRuntimeProvider.Bundled.java" :
- "NativeAotRuntimeProvider.java";
- string providerTemplate = GetResource (providerTemplateFile);
- foreach (var provider in AdditionalProviderSources) {
- var contents = providerTemplate.Replace (isMonoVM ? "MonoRuntimeProvider" : "NativeAotRuntimeProvider", provider);
- var real_provider = isMonoVM ?
- Path.Combine (OutputDirectory, "src", "mono", provider + ".java") :
- Path.Combine (OutputDirectory, "src", "net", "dot", "jni", "nativeaot", provider + ".java");
- Files.CopyIfStringChanged (contents, real_provider);
- }
+ WriteAdditionalRuntimeProviderSources (OutputDirectory, isMonoVM, AdditionalProviderSources);
// For NativeAOT, generate JavaInteropRuntime.java and NativeAotEnvironmentVars.java
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
@@ -122,6 +112,29 @@ static string GetResource (string resource)
return reader.ReadToEnd ();
}
+ ///
+ /// Writes the additional per-process runtime provider Java sources (e.g. NativeAotRuntimeProvider_1.java)
+ /// by cloning the runtime provider template for each name. Shared between the legacy (ILLink) and
+ /// trimmable build paths so both emit the extra providers a multi-process app declares in its manifest.
+ ///
+ internal static void WriteAdditionalRuntimeProviderSources (string outputDirectory, bool isMonoVM, string [] additionalProviderSources)
+ {
+ if (additionalProviderSources.Length == 0) {
+ return;
+ }
+ string providerTemplateFile = isMonoVM ?
+ "MonoRuntimeProvider.Bundled.java" :
+ "NativeAotRuntimeProvider.java";
+ string providerTemplate = GetResource (providerTemplateFile);
+ foreach (var provider in additionalProviderSources) {
+ var contents = providerTemplate.Replace (isMonoVM ? "MonoRuntimeProvider" : "NativeAotRuntimeProvider", provider);
+ var realProvider = isMonoVM ?
+ Path.Combine (outputDirectory, "src", "mono", provider + ".java") :
+ Path.Combine (outputDirectory, "src", "net", "dot", "jni", "nativeaot", provider + ".java");
+ Files.CopyIfStringChanged (contents, realProvider);
+ }
+ }
+
void SaveResource (string resource, string filename, string destDir, Func applyTemplate)
{
string template = GetResource (resource);
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs
index ba2505f71f3..39c4f4e3eb0 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs
@@ -29,11 +29,19 @@ public sealed class GenerateNativeAotBootstrapSources : AndroidTask
public bool EnableSGenConcurrent { get; set; }
+ // Names of the extra per-process runtime providers (e.g. NativeAotRuntimeProvider_1) that the
+ // manifest declares for components with a non-default android:process; their Java sources must be
+ // generated too. On the legacy path GenerateAdditionalProviderSources writes these; the trimmable
+ // path has no such task, so the bootstrap step handles them here.
+ public string [] AdditionalProviderSources { get; set; } = [];
+
public override bool RunTask ()
{
GenerateAdditionalProviderSources.GenerateNativeAotBootstrapFiles (
Log, OutputDirectory, TargetName, Environments, EnableSGenConcurrent);
+ GenerateAdditionalProviderSources.WriteAdditionalRuntimeProviderSources (OutputDirectory, isMonoVM: false, AdditionalProviderSources);
+
return !Log.HasLoggedErrors;
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotProguardConfiguration.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotProguardConfiguration.cs
index 44ae925d4f4..08e768d315f 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotProguardConfiguration.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotProguardConfiguration.cs
@@ -15,7 +15,6 @@ public class GenerateNativeAotProguardConfiguration : AndroidTask
public override string TaskPrefix => "GNAPC";
- [Required]
public ITaskItem [] NativeAotDgmlFiles { get; set; } = [];
[Required]
@@ -24,6 +23,12 @@ public class GenerateNativeAotProguardConfiguration : AndroidTask
[Required]
public string OutputFile { get; set; } = "";
+ // When false, the ILC DGML is not consulted (it may not have been generated at all) and a
+ // -keep rule is emitted for every Java Callable Wrapper in the ACW map, so R8 keeps them all
+ // instead of shrinking the unused ones. This trades a small amount of dex size for skipping the
+ // very large DGML files and the DGML parsing/scan, which dominate NativeAOT build time.
+ public bool TrimJavaCallableWrappers { get; set; } = true;
+
public override bool RunTask ()
{
var dir = Path.GetDirectoryName (OutputFile);
@@ -31,22 +36,27 @@ public override bool RunTask ()
Directory.CreateDirectory (dir);
}
- if (NativeAotDgmlFiles.Length == 0) {
- Log.LogCodedError ("XA4319", Properties.Resources.XA4319);
- return !Log.HasLoggedErrors;
- }
if (!File.Exists (AcwMapFile)) {
Log.LogCodedError ("XA4320", Properties.Resources.XA4320, AcwMapFile);
return !Log.HasLoggedErrors;
}
- foreach (var dgmlFile in NativeAotDgmlFiles) {
- if (!File.Exists (dgmlFile.ItemSpec)) {
- Log.LogCodedError ("XA4321", Properties.Resources.XA4321, dgmlFile.ItemSpec);
+
+ HashSet? retainedTypeKeys = null;
+ if (TrimJavaCallableWrappers) {
+ if (NativeAotDgmlFiles.Length == 0) {
+ Log.LogCodedError ("XA4319", Properties.Resources.XA4319);
return !Log.HasLoggedErrors;
}
+ foreach (var dgmlFile in NativeAotDgmlFiles) {
+ if (!File.Exists (dgmlFile.ItemSpec)) {
+ Log.LogCodedError ("XA4321", Properties.Resources.XA4321, dgmlFile.ItemSpec);
+ return !Log.HasLoggedErrors;
+ }
+ }
+ retainedTypeKeys = LoadRetainedTypeKeysFromDgml ();
}
- var retainedTypeKeys = LoadRetainedTypeKeysFromDgml ();
+ // A null retainedTypeKeys means "keep every ACW" (Java trimming disabled).
var javaTypes = LoadJavaTypesFromAcwMap (retainedTypeKeys);
using var writer = new StringWriter ();
@@ -56,13 +66,17 @@ public override bool RunTask ()
}
Files.CopyIfStringChanged (writer.ToString (), OutputFile);
- Log.LogMessage (MessageImportance.Low, "Generated {0} NativeAOT trimmable typemap ProGuard rules from {1} DGML file(s).", javaTypes.Count, NativeAotDgmlFiles.Length);
+ if (TrimJavaCallableWrappers) {
+ Log.LogMessage (MessageImportance.Low, "Generated {0} NativeAOT trimmable typemap ProGuard rules from {1} DGML file(s).", javaTypes.Count, NativeAotDgmlFiles.Length);
+ } else {
+ Log.LogMessage (MessageImportance.Low, "Generated {0} NativeAOT ProGuard rules keeping all ACWs (Java Callable Wrapper trimming is disabled).", javaTypes.Count);
+ }
return !Log.HasLoggedErrors;
}
- List LoadJavaTypesFromAcwMap (HashSet retainedTypeKeys)
+ List LoadJavaTypesFromAcwMap (HashSet? retainedTypeKeys)
{
- var javaTypes = new List (retainedTypeKeys.Count);
+ var javaTypes = new List (retainedTypeKeys?.Count ?? 0);
var seenJavaTypes = new HashSet (StringComparer.Ordinal);
foreach (var line in File.ReadLines (AcwMapFile)) {
var separator = line.IndexOf (";", StringComparison.Ordinal);
@@ -71,7 +85,7 @@ List LoadJavaTypesFromAcwMap (HashSet retainedTypeKeys)
}
var managedTypeName = line.Substring (0, separator);
var javaTypeName = line.Substring (separator + 1);
- if (retainedTypeKeys.Contains (managedTypeName) && seenJavaTypes.Add (javaTypeName)) {
+ if ((retainedTypeKeys == null || retainedTypeKeys.Contains (managedTypeName)) && seenJavaTypes.Add (javaTypeName)) {
javaTypes.Add (javaTypeName);
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs b/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs
index a525bab3a8c..7cf23a04b7b 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs
@@ -66,6 +66,7 @@ IEnumerable GetUserJavaTypes ()
yield break;
}
var seen = new HashSet (StringComparer.Ordinal);
+ var javaTypes = new List ();
foreach (var item in JavaSourceFiles) {
var path = item.ItemSpec;
if (path.IsNullOrEmpty () || !File.Exists (path)) {
@@ -77,9 +78,13 @@ IEnumerable GetUserJavaTypes ()
typeName = $"{package}.{typeName}";
}
if (seen.Add (typeName)) {
- yield return typeName;
+ javaTypes.Add (typeName);
}
}
+ javaTypes.Sort (StringComparer.Ordinal);
+ foreach (var javaType in javaTypes) {
+ yield return javaType;
+ }
}
internal static string? ReadJavaPackage (string path)
@@ -150,7 +155,16 @@ protected override string CreateResponseFile ()
if (EnableShrinking) {
if (UseTrimmableNativeAotProguardConfiguration && !ProguardGeneratedApplicationConfiguration.IsNullOrEmpty ()) {
- File.WriteAllText (ProguardGeneratedApplicationConfiguration, "# ACW keep rules are generated from NativeAOT ILC metadata.\n");
+ // ACW keep rules come from the DGML/acw-map-driven proguard_project_references.cfg on
+ // the trimmable path. User-authored AndroidJavaSource (Bind != true) has no managed peer
+ // and is absent from that map, so keep it here explicitly; otherwise R8 shrinks it away
+ // (e.g. dropping large unreferenced sources so an app that needs multidex no longer does).
+ using (var appcfg = File.CreateText (ProguardGeneratedApplicationConfiguration)) {
+ appcfg.WriteLine ("# ACW keep rules are generated from NativeAOT ILC metadata.");
+ foreach (var java in GetUserJavaTypes ()) {
+ appcfg.WriteLine ($"-keep class {java} {{ *; }}");
+ }
+ }
} else if (!AcwMapFile.IsNullOrEmpty ()) {
var acwMap = MonoAndroidHelper.LoadMapFile (BuildEngine4, Path.GetFullPath (AcwMapFile), StringComparer.OrdinalIgnoreCase);
var javaTypes = new List (acwMap.Values.Count);
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs
index 7a6f121832d..5ecd668649e 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs
@@ -271,6 +271,7 @@ public void Execute_GenerateNativeAotProguardConfiguration_UsesDgmlTypeMetadata
NativeAotDgmlFiles = new [] { new TaskItem (dgmlFile) },
AcwMapFile = acwMapFile,
OutputFile = outputFile,
+ TrimJavaCallableWrappers = true,
};
Assert.IsTrue (task.Execute (), "Task should succeed.");
@@ -282,6 +283,75 @@ public void Execute_GenerateNativeAotProguardConfiguration_UsesDgmlTypeMetadata
StringAssert.DoesNotContain ("other.Type", proguard);
}
+ [Test]
+ public void Execute_GenerateNativeAotProguardConfiguration_KeepsAllWhenTrimmingDisabled ()
+ {
+ var path = Path.Combine (Root, "temp", TestName);
+ var acwMapFile = Path.Combine (path, "acw-map.txt");
+ var outputFile = Path.Combine (path, "proguard", "proguard_project_references.cfg");
+ Directory.CreateDirectory (path);
+ File.WriteAllText (acwMapFile, """
+ UnnamedProject.MainActivity, UnnamedProject;crc64a1.MainActivity
+ Android.App.Activity, Mono.Android;android.app.Activity
+ Duplicate.Type, My.Assembly;my.app.Duplicate
+ Other.Type;other.Type
+ """);
+
+ // No DGML is provided: with trimming disabled the task must keep every ACW from the map
+ // rather than shrinking to the DGML-retained subset.
+ var task = new GenerateNativeAotProguardConfiguration {
+ BuildEngine = new MockBuildEngine (TestContext.Out),
+ AcwMapFile = acwMapFile,
+ OutputFile = outputFile,
+ TrimJavaCallableWrappers = false,
+ };
+
+ Assert.IsTrue (task.Execute (), "Task should succeed without a DGML when trimming is disabled.");
+ var proguard = File.ReadAllText (outputFile);
+ StringAssert.Contains ("-keep class crc64a1.MainActivity { *; }", proguard);
+ StringAssert.Contains ("-keep class android.app.Activity { *; }", proguard);
+ StringAssert.Contains ("-keep class my.app.Duplicate { *; }", proguard);
+ StringAssert.Contains ("-keep class other.Type { *; }", proguard);
+ }
+
+ [Test]
+ public void Execute_GenerateNativeAotProguardConfiguration_IgnoresDgmlWhenTrimmingDisabled ()
+ {
+ var path = Path.Combine (Root, "temp", TestName);
+ var dgmlFile = Path.Combine (path, "app.scan.dgml.xml");
+ var acwMapFile = Path.Combine (path, "acw-map.txt");
+ var outputFile = Path.Combine (path, "proguard", "proguard_project_references.cfg");
+ Directory.CreateDirectory (path);
+ File.WriteAllText (dgmlFile, """
+
+
+
+
+
+
+
+ """);
+ File.WriteAllText (acwMapFile, """
+ UnnamedProject.MainActivity, UnnamedProject;crc64a1.MainActivity
+ Android.App.Activity, Mono.Android;android.app.Activity
+ Other.Type;other.Type
+ """);
+
+ var task = new GenerateNativeAotProguardConfiguration {
+ BuildEngine = new MockBuildEngine (TestContext.Out),
+ NativeAotDgmlFiles = new [] { new TaskItem (dgmlFile) },
+ AcwMapFile = acwMapFile,
+ OutputFile = outputFile,
+ TrimJavaCallableWrappers = false,
+ };
+
+ Assert.IsTrue (task.Execute (), "Task should succeed and ignore the DGML when trimming is disabled.");
+ var proguard = File.ReadAllText (outputFile);
+ StringAssert.Contains ("-keep class crc64a1.MainActivity { *; }", proguard);
+ StringAssert.Contains ("-keep class android.app.Activity { *; }", proguard);
+ StringAssert.Contains ("-keep class other.Type { *; }", proguard);
+ }
+
GenerateTrimmableTypeMap CreateTask (ITaskItem [] assemblies, string outputDir, string javaDir,
IList? messages = null, IList? warnings = null, string tfv = "v11.0")
{