Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
<UsingTask TaskName="Xamarin.Android.Tasks.GenerateNativeAotProguardConfiguration" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />

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

Expand Down Expand Up @@ -67,27 +76,63 @@
DependsOnTargets="_GenerateTrimmableTypeMap" />

<Target Name="_CollectTrimmableNativeAotDgmlFiles"
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
Condition=" '$(_AndroidTrimmableTypemapTrimJavaCode)' == 'true' and '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
<ItemGroup>
<_TrimmableNativeAotRuntimeIdentifiers Remove="@(_TrimmableNativeAotRuntimeIdentifiers)" />
<_TrimmableNativeAotDgmlFiles Remove="@(_TrimmableNativeAotDgmlFiles)" />
<_TrimmableNativeAotCodegenDgmlFiles Remove="@(_TrimmableNativeAotCodegenDgmlFiles)" />
<_TrimmableNativeAotRuntimeIdentifiers Include="$(RuntimeIdentifier)" Condition=" '$(RuntimeIdentifier)' != '' " />
<_TrimmableNativeAotRuntimeIdentifiers Include="$(RuntimeIdentifiers)" Condition=" '$(RuntimeIdentifier)' == '' and '$(RuntimeIdentifiers)' != '' " />
<!-- RuntimeIdentifier, RuntimeIdentifiers, and no-RID publishes place ILC DGML under different intermediate paths. -->
<_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)' != '' " />
<!-- ILC writes each *.scan.dgml.xml into the per-RID inner build's NativeIntermediateOutputPath
(i.e. '<inner IntermediateOutputPath>/native/'). This target runs in the OUTER build, whose
'$(IntermediateOutputPath)' may or may not already carry the RID segment:
* a single explicit '$(RuntimeIdentifier)' set early -> the SDK already appended the RID,
so the DGML is at '$(NativeIntermediateOutputPath)' (== '$(IntermediateOutputPath)native/');
* a '$(RuntimeIdentifiers)' list, or a RID assigned late (e.g. by _GetPrimaryCpuAbi) -> the
outer path has no RID, so the DGML is at '$(IntermediateOutputPath)<rid>/native/'.
Emit both candidate paths and keep only the one(s) ILC actually produced; this stays correct
for single-RID (either flavor), multi-RID, and no-RID publishes without having to guess which
output-path shape the SDK chose. -->
<_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') " />
<!-- ILC only emits the scan graph (*.scan.dgml.xml) when its scanner phase runs (optimized/Release
builds). Unoptimized builds - e.g. Debug NativeAOT - emit only the codegen graph
(*.codegen.dgml.xml), which carries the same "Type metadata: [...]" nodes. Collect it at the same
two candidate locations and fall back to it only when no scan graph was found, so the ACW keep
rules are still generated (the codegen graph is larger, hence preferring the scan graph). -->
<_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' " />
</ItemGroup>
</Target>

<Target Name="_WriteTrimmableNativeAotProguardConfigurationInputs"
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
<MakeDir Directories="$(_AndroidStampDirectory)" Condition=" !Exists('$(_AndroidStampDirectory)') " />
<WriteLinesToFile
File="$(_TrimmableNativeAotProguardConfigurationInputsStamp)"
Lines="$(_AndroidTrimmableTypemapTrimJavaCode)"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
<ItemGroup>
<FileWrites Include="$(_TrimmableNativeAotProguardConfigurationInputsStamp)" />
</ItemGroup>
</Target>

<Target Name="_GenerateTrimmableTypeMapProguardConfiguration"
DependsOnTargets="_CollectTrimmableNativeAotDgmlFiles"
DependsOnTargets="_WriteTrimmableNativeAotProguardConfigurationInputs;_CollectTrimmableNativeAotDgmlFiles"
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' "
Inputs="@(_TrimmableNativeAotDgmlFiles);$(IntermediateOutputPath)acw-map.txt"
Inputs="$(_TrimmableNativeAotProguardConfigurationInputsStamp);@(_TrimmableNativeAotDgmlFiles);$(IntermediateOutputPath)acw-map.txt"
Outputs="$(_ProguardProjectConfiguration)">
<GenerateNativeAotProguardConfiguration
NativeAotDgmlFiles="@(_TrimmableNativeAotDgmlFiles)"
AcwMapFile="$(IntermediateOutputPath)acw-map.txt"
TrimJavaCallableWrappers="$(_AndroidTrimmableTypemapTrimJavaCode)"
OutputFile="$(_ProguardProjectConfiguration)" />
Comment on lines 132 to 136
<ItemGroup>
<FileWrites Include="$(_ProguardProjectConfiguration)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
stays incremental while still reacting to post-trim JCW regeneration. -->
<_TrimmableJavaSourceStamp Condition=" '$(_TrimmableJavaSourceStamp)' == '' and '$(_AndroidRuntime)' == 'CoreCLR' and '$(PublishTrimmed)' == 'true' ">$(_PostTrimTrimmableTypeMapJavaStamp)</_TrimmableJavaSourceStamp>
<_TrimmableJavaSourceStamp Condition=" '$(_TrimmableJavaSourceStamp)' == '' ">$(_TrimmableTypeMapOutputStamp)</_TrimmableJavaSourceStamp>
<!-- On NativeAOT, ensure ILC's dynamic build properties (e.g. RuntimeIdentifier-derived
paths) are computed before the typemap is generated. -->
<_GenerateTrimmableTypeMapDependsOn Condition=" '$(_AndroidRuntime)' == 'NativeAOT' ">$(IlcDynamicBuildPropertyDependencies)</_GenerateTrimmableTypeMapDependsOn>
<!-- _GetLibraryImports populates @(ExtractedManifestDocuments) (the extracted library .aar
AndroidManifest.xml files) that the legacy manifest merger merges into the app manifest. -->
<_GenerateTrimmableTypeMapDependsOn>$(_GenerateTrimmableTypeMapDependsOn);_GetLibraryImports</_GenerateTrimmableTypeMapDependsOn>
<!-- Max array rank for __ArrayMapRank{N} sentinel emission. Defaults to 3 when
dynamic code is unavailable, so array creation uses the typemap path;
defaults to 0 otherwise, where dynamic code can use Array.CreateInstance directly. -->
Expand Down Expand Up @@ -98,7 +104,7 @@
<Target Name="_GenerateTrimmableTypeMap"
Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' and '$(DesignTimeBuild)' != 'true' and '@(ReferencePath->Count())' != '0' and '$(_OuterIntermediateOutputPath)' == '' "
AfterTargets="CoreCompile"
DependsOnTargets="_GetLibraryImports"
DependsOnTargets="$(_GenerateTrimmableTypeMapDependsOn)"
Inputs="@(ReferencePath);@(PrivateSdkAssemblies);@(FrameworkAssemblies);@(ExtractedManifestDocuments);$(IntermediateOutputPath)$(TargetFileName);$(_AndroidManifestAbs);$(_AndroidBuildPropertiesCache)"
Outputs="$(_TrimmableTypeMapOutputStamp)">

Expand Down Expand Up @@ -375,6 +381,7 @@
OutputDirectory="$(IntermediateOutputPath)android"
TargetName="$(TargetName)"
Environments="@(_EnvironmentFiles)"
AdditionalProviderSources="@(_AdditionalProviderSources)"
EnableSGenConcurrent="$(AndroidEnableSGenConcurrent)" />

<!-- Copy generated manifest to expected location -->
Expand All @@ -383,6 +390,15 @@
SkipUnchangedFiles="true"
Condition="Exists('$(_TypeMapBaseOutputDir)AndroidManifest.xml')" />

<!-- The manifestmerger.jar path runs _ManifestMerger to produce android\AndroidManifest.xml from the
generated manifest. The legacy merger has no equivalent step, so on the trimmable path (where the
merged manifest was already produced, placeholders included, by _GenerateTrimmableTypeMap) copy it
into place directly; otherwise _ReadAndroidManifest and downstream packaging cannot find it. -->
<Copy SourceFiles="$(_TypeMapBaseOutputDir)AndroidManifest.xml"
DestinationFiles="$(IntermediateOutputPath)android\AndroidManifest.xml"
SkipUnchangedFiles="true"
Condition=" '$(AndroidManifestMerger)' != 'manifestmerger.jar' and Exists('$(_TypeMapBaseOutputDir)AndroidManifest.xml') " />

<!-- acw-map.txt is populated by _GenerateTrimmableTypeMap with real managed→Java mappings.
If that target was skipped (e.g., no input assemblies), create an empty placeholder so
downstream targets (_ConvertCustomView, _UpdateAndroidResgen) can evaluate their Inputs. -->
Expand All @@ -392,6 +408,8 @@
<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)android/src/net/dot/android/ApplicationRegistration.java" />
<FileWrites Include="$(IntermediateOutputPath)AndroidManifest.xml" />
<FileWrites Include="$(IntermediateOutputPath)android\AndroidManifest.xml"
Condition=" '$(AndroidManifestMerger)' != 'manifestmerger.jar' " />
<FileWrites Include="$(IntermediateOutputPath)acw-map.txt" />
</ItemGroup>

Expand All @@ -418,4 +436,41 @@
<FileWrites Include="@(_TypeMapAssemblySource)" />
</ItemGroup>
</Target>

<!--
Keep the resource designer assembly on the trimmable type map path.

The non-trimmable path (Microsoft.Android.Sdk.TypeMap.LlvmIr.targets) runs
PreTrimmingFixLegacyDesigner, which rewrites legacy resource field loads into designer
property-getter calls so the trimmer can drop unused designer members. The trimmable path
does not run that rewrite. More importantly, resource ids are resolved via reflection at
runtime (Android.Runtime.ResourceIdManager then ResourceDesignerAttribute then Type.GetType /
UpdateIdValues), which neither ILLink nor ILC can follow. If the designer assembly is left
trimmable it gets removed entirely, and accessing any resource id (e.g. from a reflection-only
code path) throws a TypeInitializationException wrapping
System.IO.FileNotFoundException: _Microsoft.Android.Resource.Designer at runtime.

Keep the whole _Microsoft.Android.Resource.Designer assembly. Two things are required on the
NativeAOT (ILC) path:
* ILC only loads the assemblies passed to it as references; the designer is a compile-time
reference but is NOT in @(IlcReference), so add it. Rooting an assembly ILC never loaded
fails hard with "Failed to load assembly '_Microsoft.Android.Resource.Designer'".
* Root it so ILC/ILLink keep it whole (TrimMode-based rooting can't be used here because
_AndroidComputeIlcCompileInputs clears @(_IlcManagedInputAssemblies)).

Drive both off @(ReferencePath) filtered to the designer: the item is empty when the designer
assembly was not generated (e.g. no resources / AndroidGenerateResourceDesigner=false), so we
never reference or root a non-existent assembly. @(IlcReference) is ignored by ILLink, so the
CoreCLR path just gets the root.
-->
<Target Name="_RootResourceDesignerForTrimmableTypeMap"
Condition=" '$(AndroidUseDesignerAssembly)' == 'True' and '$(PublishTrimmed)' == 'true' "
BeforeTargets="PrepareForILLink;WriteIlcRspFileForCompilation">
<ItemGroup>
<_ResourceDesignerReferenceToRoot Include="@(ReferencePath)"
Condition=" '%(Filename)' == '_Microsoft.Android.Resource.Designer' " />
<IlcReference Include="@(_ResourceDesignerReferenceToRoot->'%(FullPath)')" />
<TrimmerRootAssembly Include="@(_ResourceDesignerReferenceToRoot->'%(Filename)')" />
</ItemGroup>
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -122,6 +112,29 @@ static string GetResource (string resource)
return reader.ReadToEnd ();
}

/// <summary>
/// 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.
/// </summary>
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<string, string> applyTemplate)
{
string template = GetResource (resource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading
Loading