Skip to content

Commit d368a60

Browse files
[TrimmableTypeMap][NativeAOT] Build plumbing fixes (#11989)
Independent build-plumbing fixes for the **trimmable type map on NativeAOT** (the opt-in `-p:_AndroidTypeMapImplementation=trimmable` + `_AndroidRuntime=NativeAOT` path). These stand alone and do **not** depend on making the trimmable type map the NativeAOT default (#11822) — extracting them keeps that PR focused. ### Fixes - **ILC DGML collection** (3 commits): fix XA4321 by collecting the ILC DGML from the per-RID **inner** build path; handle both single-RID output-path shapes; and fall back to the codegen DGML when the scan DGML is absent. These make the ACW/proguard keep-rule generation robust across the RID-nested inner-build layout. - **Fix `ManifestPlaceholders` on the trimmable NativeAOT path** — placeholder substitution in the generated manifest now works (`ManifestGenerator`). - **`_AndroidTrimmableTypemapTrimJavaCode` toggle (default off)** — optionally skip DGML parsing and keep all ACWs, trading a little dex size for skipping the very large DGML files that dominate NativeAOT build time. `GenerateNativeAotProguardConfiguration` no longer requires DGML input when disabled. - **Generate per-process runtime provider Java sources** — share provider-source generation between MonoVM and NativeAOT (`GenerateAdditionalProviderSources` / `GenerateNativeAotBootstrapSources`). - **Keep user `AndroidJavaSource` under R8** — user-authored Java (no managed peer) is absent from the ACW map, so emit explicit `-keep` rules; otherwise R8 shrinks it away (which can, e.g., drop sources so an app that needs multidex no longer does). - **Keep the `_Microsoft.Android.Resource.Designer` assembly** — resource ids are resolved via reflection (`ResourceIdManager`), which the trimmer can't follow; root the designer via `TrimmerRootAssembly` (honored by both ILLink and ILC) so it isn't trimmed away (otherwise resource-id access throws `FileNotFoundException: _Microsoft.Android.Resource.Designer`). ### Testing - `Microsoft.Android.Sdk.TrimmableTypeMap` generator + `GenerateTrimmableTypeMapTests` build clean; full validation via CI (device tests) since these exercise the on-device NativeAOT path. Extracted from #11822. ## Issue references Partially addresses #11774. Contributes to #10790 and #10793. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 44fbb8a commit d368a60

9 files changed

Lines changed: 290 additions & 39 deletions

File tree

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,15 @@ internal static void ApplyPlaceholders (XDocument doc, string? placeholders, str
518518
var eqIndex = entry.IndexOf ('=');
519519
if (eqIndex >= 0) {
520520
var key = entry.Substring (0, eqIndex).Trim ();
521-
var value = entry.Substring (eqIndex + 1).Trim ();
521+
// Normalize '\' to Path.DirectorySeparatorChar to stay byte-for-byte identical to the
522+
// legacy pipeline on every platform: there the substituted manifest is re-encoded by
523+
// aapt2, which rewrites backslashes to the platform separator ('/' on Unix, '\' preserved
524+
// on Windows) across the whole manifest. The trimmable generator writes the merged
525+
// manifest directly (no aapt2 re-encode of these values), so it applies the same
526+
// per-platform normalization to every value. The ManifestPlaceholders build test pins
527+
// this for both the legacy (CoreCLR) and trimmable (NativeAOT) paths, so a hardcoded '/'
528+
// would fail on Windows.
529+
var value = entry.Substring (eqIndex + 1).Trim ().Replace ('\\', Path.DirectorySeparatorChar);
522530
replacements ["${" + key + "}"] = value;
523531
} else if (eqIndex < 0) {
524532
// An entry without '=' is not a valid key=value pair. Mirror the legacy

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,22 @@
55
<UsingTask TaskName="Xamarin.Android.Tasks.GenerateNativeAotProguardConfiguration" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
66

77
<PropertyGroup>
8+
<!-- Controls DGML-based trimming of Java Callable Wrappers on the trimmable NativeAOT path.
9+
When true, ILC emits a DGML dependency graph and _GenerateTrimmableTypeMapProguardConfiguration
10+
turns it into R8 -keep rules so R8 can shrink the unused JCWs from classes.dex. Those DGML files
11+
are very large (hundreds of MB) and dominate build time, so this defaults to false until ILC/illink
12+
expose a leaner typemap dump: R8 still runs, but every JCW is kept (a few hundred kB of extra dex)
13+
and the DGML is never generated. -->
14+
<_AndroidTrimmableTypemapTrimJavaCode Condition=" '$(_AndroidTrimmableTypemapTrimJavaCode)' == '' ">false</_AndroidTrimmableTypemapTrimJavaCode>
815
<_TrimmableRuntimeProviderJavaName Condition=" '$(_TrimmableRuntimeProviderJavaName)' == '' ">net.dot.jni.nativeaot.NativeAotRuntimeProvider</_TrimmableRuntimeProviderJavaName>
916
<AndroidLinkTool Condition=" '$(AndroidLinkTool)' == '' ">r8</AndroidLinkTool>
1017
<AndroidDexTool Condition=" '$(AndroidLinkTool)' == 'r8' ">d8</AndroidDexTool>
1118
<AndroidEnableProguard Condition=" '$(AndroidLinkTool)' != '' ">True</AndroidEnableProguard>
1219
<AndroidCreateProguardMappingFile Condition=" '$(AndroidCreateProguardMappingFile)' == '' and '$(AndroidLinkTool)' == 'r8' ">True</AndroidCreateProguardMappingFile>
13-
<IlcGenerateDgmlFile Condition=" '$(AndroidLinkTool)' != '' and '$(IlcGenerateDgmlFile)' == '' ">true</IlcGenerateDgmlFile>
20+
<!-- Only ask ILC for the DGML when JCW trimming is enabled; it is only consumed to compute the keep rules. -->
21+
<IlcGenerateDgmlFile Condition=" '$(_AndroidTrimmableTypemapTrimJavaCode)' == 'true' and '$(AndroidLinkTool)' != '' and '$(IlcGenerateDgmlFile)' == '' ">true</IlcGenerateDgmlFile>
1422
<_UseTrimmableNativeAotProguardConfiguration Condition=" '$(_UseTrimmableNativeAotProguardConfiguration)' == '' ">true</_UseTrimmableNativeAotProguardConfiguration>
23+
<_TrimmableNativeAotProguardConfigurationInputsStamp>$(_AndroidStampDirectory)_GenerateTrimmableTypeMapProguardConfiguration.inputs</_TrimmableNativeAotProguardConfigurationInputsStamp>
1524
<_CompileToDalvikDependsOnTargets>$(_CompileToDalvikDependsOnTargets);_GenerateTrimmableTypeMapProguardConfiguration</_CompileToDalvikDependsOnTargets>
1625
</PropertyGroup>
1726

@@ -140,27 +149,63 @@
140149
</Target>
141150

142151
<Target Name="_CollectTrimmableNativeAotDgmlFiles"
143-
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
152+
Condition=" '$(_AndroidTrimmableTypemapTrimJavaCode)' == 'true' and '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
144153
<ItemGroup>
145154
<_TrimmableNativeAotRuntimeIdentifiers Remove="@(_TrimmableNativeAotRuntimeIdentifiers)" />
146155
<_TrimmableNativeAotDgmlFiles Remove="@(_TrimmableNativeAotDgmlFiles)" />
156+
<_TrimmableNativeAotCodegenDgmlFiles Remove="@(_TrimmableNativeAotCodegenDgmlFiles)" />
147157
<_TrimmableNativeAotRuntimeIdentifiers Include="$(RuntimeIdentifier)" Condition=" '$(RuntimeIdentifier)' != '' " />
148158
<_TrimmableNativeAotRuntimeIdentifiers Include="$(RuntimeIdentifiers)" Condition=" '$(RuntimeIdentifier)' == '' and '$(RuntimeIdentifiers)' != '' " />
149-
<!-- RuntimeIdentifier, RuntimeIdentifiers, and no-RID publishes place ILC DGML under different intermediate paths. -->
150-
<_TrimmableNativeAotDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml" Condition=" '@(_TrimmableNativeAotRuntimeIdentifiers)' == '' " />
151-
<_TrimmableNativeAotDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml" Condition=" '$(RuntimeIdentifier)' != '' " />
152-
<_TrimmableNativeAotDgmlFiles Include="$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).scan.dgml.xml" Condition=" '$(RuntimeIdentifier)' == '' and '@(_TrimmableNativeAotRuntimeIdentifiers)' != '' " />
159+
<!-- ILC writes each *.scan.dgml.xml into the per-RID inner build's NativeIntermediateOutputPath
160+
(i.e. '<inner IntermediateOutputPath>/native/'). This target runs in the OUTER build, whose
161+
'$(IntermediateOutputPath)' may or may not already carry the RID segment:
162+
* a single explicit '$(RuntimeIdentifier)' set early -> the SDK already appended the RID,
163+
so the DGML is at '$(NativeIntermediateOutputPath)' (== '$(IntermediateOutputPath)native/');
164+
* a '$(RuntimeIdentifiers)' list, or a RID assigned late (e.g. by _GetPrimaryCpuAbi) -> the
165+
outer path has no RID, so the DGML is at '$(IntermediateOutputPath)<rid>/native/'.
166+
Emit both candidate paths and keep only the one(s) ILC actually produced; this stays correct
167+
for single-RID (either flavor), multi-RID, and no-RID publishes without having to guess which
168+
output-path shape the SDK chose. -->
169+
<_TrimmableNativeAotDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml"
170+
Condition=" Exists('$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml') " />
171+
<_TrimmableNativeAotDgmlFiles Include="$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).scan.dgml.xml"
172+
Condition=" '@(_TrimmableNativeAotRuntimeIdentifiers)' != '' and Exists('$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).scan.dgml.xml') " />
173+
<!-- ILC only emits the scan graph (*.scan.dgml.xml) when its scanner phase runs (optimized/Release
174+
builds). Unoptimized builds - e.g. Debug NativeAOT - emit only the codegen graph
175+
(*.codegen.dgml.xml), which carries the same "Type metadata: [...]" nodes. Collect it at the same
176+
two candidate locations and fall back to it only when no scan graph was found, so the ACW keep
177+
rules are still generated (the codegen graph is larger, hence preferring the scan graph). -->
178+
<_TrimmableNativeAotCodegenDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).codegen.dgml.xml"
179+
Condition=" Exists('$(NativeIntermediateOutputPath)$(TargetName).codegen.dgml.xml') " />
180+
<_TrimmableNativeAotCodegenDgmlFiles Include="$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).codegen.dgml.xml"
181+
Condition=" '@(_TrimmableNativeAotRuntimeIdentifiers)' != '' and Exists('$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).codegen.dgml.xml') " />
182+
<_TrimmableNativeAotDgmlFiles Include="@(_TrimmableNativeAotCodegenDgmlFiles)"
183+
Condition=" '@(_TrimmableNativeAotDgmlFiles->Count())' == '0' " />
184+
</ItemGroup>
185+
</Target>
186+
187+
<Target Name="_WriteTrimmableNativeAotProguardConfigurationInputs"
188+
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
189+
<MakeDir Directories="$(_AndroidStampDirectory)" Condition=" !Exists('$(_AndroidStampDirectory)') " />
190+
<WriteLinesToFile
191+
File="$(_TrimmableNativeAotProguardConfigurationInputsStamp)"
192+
Lines="$(_AndroidTrimmableTypemapTrimJavaCode)"
193+
Overwrite="true"
194+
WriteOnlyWhenDifferent="true" />
195+
<ItemGroup>
196+
<FileWrites Include="$(_TrimmableNativeAotProguardConfigurationInputsStamp)" />
153197
</ItemGroup>
154198
</Target>
155199

156200
<Target Name="_GenerateTrimmableTypeMapProguardConfiguration"
157-
DependsOnTargets="_CollectTrimmableNativeAotDgmlFiles"
201+
DependsOnTargets="_WriteTrimmableNativeAotProguardConfigurationInputs;_CollectTrimmableNativeAotDgmlFiles"
158202
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' "
159-
Inputs="@(_TrimmableNativeAotDgmlFiles);$(IntermediateOutputPath)acw-map.txt"
203+
Inputs="$(_TrimmableNativeAotProguardConfigurationInputsStamp);@(_TrimmableNativeAotDgmlFiles);$(IntermediateOutputPath)acw-map.txt"
160204
Outputs="$(_ProguardProjectConfiguration)">
161205
<GenerateNativeAotProguardConfiguration
162206
NativeAotDgmlFiles="@(_TrimmableNativeAotDgmlFiles)"
163207
AcwMapFile="$(IntermediateOutputPath)acw-map.txt"
208+
TrimJavaCallableWrappers="$(_AndroidTrimmableTypemapTrimJavaCode)"
164209
OutputFile="$(_ProguardProjectConfiguration)" />
165210
<ItemGroup>
166211
<FileWrites Include="$(_ProguardProjectConfiguration)" />

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@
4444
stays incremental while still reacting to post-trim JCW regeneration. -->
4545
<_TrimmableJavaSourceStamp Condition=" '$(_TrimmableJavaSourceStamp)' == '' and '$(_AndroidRuntime)' == 'CoreCLR' and '$(PublishTrimmed)' == 'true' ">$(_PostTrimTrimmableTypeMapJavaStamp)</_TrimmableJavaSourceStamp>
4646
<_TrimmableJavaSourceStamp Condition=" '$(_TrimmableJavaSourceStamp)' == '' ">$(_TrimmableTypeMapOutputStamp)</_TrimmableJavaSourceStamp>
47+
<!-- On NativeAOT, ensure ILC's dynamic build properties (e.g. RuntimeIdentifier-derived
48+
paths) are computed before the typemap is generated. -->
49+
<_GenerateTrimmableTypeMapDependsOn Condition=" '$(_AndroidRuntime)' == 'NativeAOT' ">$(IlcDynamicBuildPropertyDependencies)</_GenerateTrimmableTypeMapDependsOn>
50+
<!-- _GetLibraryImports populates @(ExtractedManifestDocuments) (the extracted library .aar
51+
AndroidManifest.xml files) that the legacy manifest merger merges into the app manifest. -->
52+
<_GenerateTrimmableTypeMapDependsOn>$(_GenerateTrimmableTypeMapDependsOn);_GetLibraryImports</_GenerateTrimmableTypeMapDependsOn>
4753
<!-- Max array rank for __ArrayMapRank{N} sentinel emission. Defaults to 3 when
4854
dynamic code is unavailable, so array creation uses the typemap path;
4955
defaults to 0 otherwise, where dynamic code can use Array.CreateInstance directly. -->
@@ -98,7 +104,7 @@
98104
<Target Name="_GenerateTrimmableTypeMap"
99105
Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' and '$(DesignTimeBuild)' != 'true' and '@(ReferencePath->Count())' != '0' and '$(_OuterIntermediateOutputPath)' == '' "
100106
AfterTargets="CoreCompile"
101-
DependsOnTargets="_GetLibraryImports"
107+
DependsOnTargets="$(_GenerateTrimmableTypeMapDependsOn)"
102108
Inputs="@(ReferencePath);@(PrivateSdkAssemblies);@(FrameworkAssemblies);@(ExtractedManifestDocuments);@(_AndroidTrimmableTypeMapExtraFrameworkAssembly);$(IntermediateOutputPath)$(TargetFileName);$(_AndroidManifestAbs);$(_AndroidBuildPropertiesCache)"
103109
Outputs="$(_TrimmableTypeMapOutputStamp)">
104110

@@ -380,6 +386,7 @@
380386
OutputDirectory="$(IntermediateOutputPath)android"
381387
TargetName="$(TargetName)"
382388
Environments="@(_EnvironmentFiles)"
389+
AdditionalProviderSources="@(_AdditionalProviderSources)"
383390
EnableSGenConcurrent="$(AndroidEnableSGenConcurrent)" />
384391

385392
<!-- Copy generated manifest to expected location -->
@@ -388,6 +395,15 @@
388395
SkipUnchangedFiles="true"
389396
Condition="Exists('$(_TypeMapBaseOutputDir)AndroidManifest.xml')" />
390397

398+
<!-- The manifestmerger.jar path runs _ManifestMerger to produce android\AndroidManifest.xml from the
399+
generated manifest. The legacy merger has no equivalent step, so on the trimmable path (where the
400+
merged manifest was already produced, placeholders included, by _GenerateTrimmableTypeMap) copy it
401+
into place directly; otherwise _ReadAndroidManifest and downstream packaging cannot find it. -->
402+
<Copy SourceFiles="$(_TypeMapBaseOutputDir)AndroidManifest.xml"
403+
DestinationFiles="$(IntermediateOutputPath)android\AndroidManifest.xml"
404+
SkipUnchangedFiles="true"
405+
Condition=" '$(AndroidManifestMerger)' != 'manifestmerger.jar' and Exists('$(_TypeMapBaseOutputDir)AndroidManifest.xml') " />
406+
391407
<!-- acw-map.txt is populated by _GenerateTrimmableTypeMap with real managed→Java mappings.
392408
If that target was skipped (e.g., no input assemblies), create an empty placeholder so
393409
downstream targets (_ConvertCustomView, _UpdateAndroidResgen) can evaluate their Inputs. -->
@@ -397,6 +413,8 @@
397413
<ItemGroup>
398414
<FileWrites Include="$(IntermediateOutputPath)android/src/net/dot/android/ApplicationRegistration.java" />
399415
<FileWrites Include="$(IntermediateOutputPath)AndroidManifest.xml" />
416+
<FileWrites Include="$(IntermediateOutputPath)android\AndroidManifest.xml"
417+
Condition=" '$(AndroidManifestMerger)' != 'manifestmerger.jar' " />
400418
<FileWrites Include="$(IntermediateOutputPath)acw-map.txt" />
401419
</ItemGroup>
402420

@@ -423,4 +441,41 @@
423441
<FileWrites Include="@(_TypeMapAssemblySource)" />
424442
</ItemGroup>
425443
</Target>
444+
445+
<!--
446+
Keep the resource designer assembly on the trimmable type map path.
447+
448+
The non-trimmable path (Microsoft.Android.Sdk.TypeMap.LlvmIr.targets) runs
449+
PreTrimmingFixLegacyDesigner, which rewrites legacy resource field loads into designer
450+
property-getter calls so the trimmer can drop unused designer members. The trimmable path
451+
does not run that rewrite. More importantly, resource ids are resolved via reflection at
452+
runtime (Android.Runtime.ResourceIdManager then ResourceDesignerAttribute then Type.GetType /
453+
UpdateIdValues), which neither ILLink nor ILC can follow. If the designer assembly is left
454+
trimmable it gets removed entirely, and accessing any resource id (e.g. from a reflection-only
455+
code path) throws a TypeInitializationException wrapping
456+
System.IO.FileNotFoundException: _Microsoft.Android.Resource.Designer at runtime.
457+
458+
Keep the whole _Microsoft.Android.Resource.Designer assembly. Two things are required on the
459+
NativeAOT (ILC) path:
460+
* ILC only loads the assemblies passed to it as references; the designer is a compile-time
461+
reference but is NOT in @(IlcReference), so add it. Rooting an assembly ILC never loaded
462+
fails hard with "Failed to load assembly '_Microsoft.Android.Resource.Designer'".
463+
* Root it so ILC/ILLink keep it whole (TrimMode-based rooting can't be used here because
464+
_AndroidComputeIlcCompileInputs clears @(_IlcManagedInputAssemblies)).
465+
466+
Drive both off @(ReferencePath) filtered to the designer: the item is empty when the designer
467+
assembly was not generated (e.g. no resources / AndroidGenerateResourceDesigner=false), so we
468+
never reference or root a non-existent assembly. @(IlcReference) is ignored by ILLink, so the
469+
CoreCLR path just gets the root.
470+
-->
471+
<Target Name="_RootResourceDesignerForTrimmableTypeMap"
472+
Condition=" '$(AndroidUseDesignerAssembly)' == 'True' and '$(PublishTrimmed)' == 'true' "
473+
BeforeTargets="PrepareForILLink;WriteIlcRspFileForCompilation">
474+
<ItemGroup>
475+
<_ResourceDesignerReferenceToRoot Include="@(ReferencePath)"
476+
Condition=" '%(Filename)' == '_Microsoft.Android.Resource.Designer' " />
477+
<IlcReference Include="@(_ResourceDesignerReferenceToRoot->'%(FullPath)')" />
478+
<TrimmerRootAssembly Include="@(_ResourceDesignerReferenceToRoot->'%(Filename)')" />
479+
</ItemGroup>
480+
</Target>
426481
</Project>

src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,8 @@ void Generate (NativeCodeGenStateObject codeGenState)
7070
Xamarin.Android.Tasks.AndroidRuntime.CoreCLR => true,
7171
_ => false,
7272
};
73-
string providerTemplateFile = isMonoVM ?
74-
"MonoRuntimeProvider.Bundled.java" :
75-
"NativeAotRuntimeProvider.java";
76-
string providerTemplate = GetResource (providerTemplateFile);
7773

78-
foreach (var provider in AdditionalProviderSources) {
79-
var contents = providerTemplate.Replace (isMonoVM ? "MonoRuntimeProvider" : "NativeAotRuntimeProvider", provider);
80-
var real_provider = isMonoVM ?
81-
Path.Combine (OutputDirectory, "src", "mono", provider + ".java") :
82-
Path.Combine (OutputDirectory, "src", "net", "dot", "jni", "nativeaot", provider + ".java");
83-
Files.CopyIfStringChanged (contents, real_provider);
84-
}
74+
WriteAdditionalRuntimeProviderSources (OutputDirectory, isMonoVM, AdditionalProviderSources);
8575

8676
// For NativeAOT, generate JavaInteropRuntime.java and NativeAotEnvironmentVars.java
8777
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
@@ -122,6 +112,29 @@ static string GetResource (string resource)
122112
return reader.ReadToEnd ();
123113
}
124114

115+
/// <summary>
116+
/// Writes the additional per-process runtime provider Java sources (e.g. NativeAotRuntimeProvider_1.java)
117+
/// by cloning the runtime provider template for each name. Shared between the legacy (ILLink) and
118+
/// trimmable build paths so both emit the extra providers a multi-process app declares in its manifest.
119+
/// </summary>
120+
internal static void WriteAdditionalRuntimeProviderSources (string outputDirectory, bool isMonoVM, string [] additionalProviderSources)
121+
{
122+
if (additionalProviderSources.Length == 0) {
123+
return;
124+
}
125+
string providerTemplateFile = isMonoVM ?
126+
"MonoRuntimeProvider.Bundled.java" :
127+
"NativeAotRuntimeProvider.java";
128+
string providerTemplate = GetResource (providerTemplateFile);
129+
foreach (var provider in additionalProviderSources) {
130+
var contents = providerTemplate.Replace (isMonoVM ? "MonoRuntimeProvider" : "NativeAotRuntimeProvider", provider);
131+
var realProvider = isMonoVM ?
132+
Path.Combine (outputDirectory, "src", "mono", provider + ".java") :
133+
Path.Combine (outputDirectory, "src", "net", "dot", "jni", "nativeaot", provider + ".java");
134+
Files.CopyIfStringChanged (contents, realProvider);
135+
}
136+
}
137+
125138
void SaveResource (string resource, string filename, string destDir, Func<string, string> applyTemplate)
126139
{
127140
string template = GetResource (resource);

src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,19 @@ public sealed class GenerateNativeAotBootstrapSources : AndroidTask
2929

3030
public bool EnableSGenConcurrent { get; set; }
3131

32+
// Names of the extra per-process runtime providers (e.g. NativeAotRuntimeProvider_1) that the
33+
// manifest declares for components with a non-default android:process; their Java sources must be
34+
// generated too. On the legacy path GenerateAdditionalProviderSources writes these; the trimmable
35+
// path has no such task, so the bootstrap step handles them here.
36+
public string [] AdditionalProviderSources { get; set; } = [];
37+
3238
public override bool RunTask ()
3339
{
3440
GenerateAdditionalProviderSources.GenerateNativeAotBootstrapFiles (
3541
Log, OutputDirectory, TargetName, Environments, EnableSGenConcurrent);
3642

43+
GenerateAdditionalProviderSources.WriteAdditionalRuntimeProviderSources (OutputDirectory, isMonoVM: false, AdditionalProviderSources);
44+
3745
return !Log.HasLoggedErrors;
3846
}
3947
}

0 commit comments

Comments
 (0)