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 @@ -25,66 +25,39 @@
</PropertyGroup>
</Target>

<!-- Add TypeMap DLLs to assembly store with per-ABI metadata.
TypeMap DLLs are ABI-agnostic (pure IL), so the same DLLs are added for every ABI.
This target batches over @(_BuildTargetAbis) to add items per-ABI. -->
<Target Name="_AddTrimmableTypeMapAssembliesToStore"
BeforeTargets="_BuildApkEmbed"
DependsOnTargets="_PrepareTrimmableTypeMapAssemblies;_DefineBuildTargetAbis">
<!-- Add linked TypeMap DLLs to the normal publish assembly pipeline. The SDK R2R
target only compiles ResolvedFileToPublish items with PostprocessAssembly=true. -->
<Target Name="_AddTrimmableTypeMapToResolvedFileToPublish"
Condition=" '$(RuntimeIdentifier)' != '' "
AfterTargets="ILLink"
BeforeTargets="CreateReadyToRunImages"
DependsOnTargets="_ReadGeneratedTrimmableTypeMapAssemblies">
<ItemGroup>
<_TrimmableTypeMapAbi Include="@(_BuildTargetAbis)" />
<_TrimmableTypeMapAbi Update="@(_TrimmableTypeMapAbi)" Condition=" '%(_TrimmableTypeMapAbi.Identity)' == 'arm64-v8a' ">
<RuntimeIdentifier>android-arm64</RuntimeIdentifier>
</_TrimmableTypeMapAbi>
<_TrimmableTypeMapAbi Update="@(_TrimmableTypeMapAbi)" Condition=" '%(_TrimmableTypeMapAbi.Identity)' == 'armeabi-v7a' ">
<RuntimeIdentifier>android-arm</RuntimeIdentifier>
</_TrimmableTypeMapAbi>
<_TrimmableTypeMapAbi Update="@(_TrimmableTypeMapAbi)" Condition=" '%(_TrimmableTypeMapAbi.Identity)' == 'x86_64' ">
<RuntimeIdentifier>android-x64</RuntimeIdentifier>
</_TrimmableTypeMapAbi>
<_TrimmableTypeMapAbi Update="@(_TrimmableTypeMapAbi)" Condition=" '%(_TrimmableTypeMapAbi.Identity)' == 'x86' ">
<RuntimeIdentifier>android-x86</RuntimeIdentifier>
</_TrimmableTypeMapAbi>
<_TrimmableTypeMapLinkedAssemblies Include="$(IntermediateOutputPath)linked\_*.TypeMap.dll;$(IntermediateOutputPath)linked\_Microsoft.Android.TypeMap*.dll" />
<_TrimmableTypeMapAssembliesToPublish Include="@(_TrimmableTypeMapLinkedAssemblies)" />
<_TrimmableTypeMapAssembliesToPublish Include="@(_GeneratedTypeMapAssembliesFromList)" Condition=" '@(_TrimmableTypeMapLinkedAssemblies->Count())' == '0' " />
<ResolvedFileToPublish Include="@(_TrimmableTypeMapAssembliesToPublish)">
<RelativePath>%(Filename)%(Extension)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<PostprocessAssembly>true</PostprocessAssembly>
<RuntimeIdentifier>$(RuntimeIdentifier)</RuntimeIdentifier>
</ResolvedFileToPublish>
</ItemGroup>
</Target>

<!-- Try linked/ first (trimmed by ILLink in inner builds) -->
<ItemGroup>
<_LinkedTypeMapDlls Include="$(IntermediateOutputPath)%(_TrimmableTypeMapAbi.RuntimeIdentifier)/linked/_*.TypeMap.dll;$(IntermediateOutputPath)%(_TrimmableTypeMapAbi.RuntimeIdentifier)/linked/_Microsoft.Android.TypeMap*.dll">
<Abi>%(_TrimmableTypeMapAbi.Identity)</Abi>
<RuntimeIdentifier>%(_TrimmableTypeMapAbi.RuntimeIdentifier)</RuntimeIdentifier>
<DestinationSubPath>%(_TrimmableTypeMapAbi.Identity)/%(_LinkedTypeMapDlls.Filename)%(_LinkedTypeMapDlls.Extension)</DestinationSubPath>
<DestinationSubDirectory>%(_TrimmableTypeMapAbi.Identity)/</DestinationSubDirectory>
</_LinkedTypeMapDlls>
</ItemGroup>
<ItemGroup Condition=" '@(_LinkedTypeMapDlls->Count())' != '0' ">
<_BuildApkResolvedUserAssemblies Include="@(_LinkedTypeMapDlls)">
<Abi>%(_LinkedTypeMapDlls.Abi)</Abi>
<RuntimeIdentifier>%(_LinkedTypeMapDlls.RuntimeIdentifier)</RuntimeIdentifier>
<DestinationSubPath>%(_LinkedTypeMapDlls.DestinationSubPath)</DestinationSubPath>
<DestinationSubDirectory>%(_LinkedTypeMapDlls.DestinationSubDirectory)</DestinationSubDirectory>
</_BuildApkResolvedUserAssemblies>
</ItemGroup>
<!-- Fallback: use untrimmed TypeMap DLLs when linked/ is empty (Debug builds without ILLink). -->
<ItemGroup Condition=" '@(_LinkedTypeMapDlls->Count())' == '0' and '@(_TrimmableTypeMapAbi)' != '' ">
<_TypeMapDlls Include="$(_TypeMapOutputDirectory)*.dll">
<Abi>%(_TrimmableTypeMapAbi.Identity)</Abi>
<RuntimeIdentifier>%(_TrimmableTypeMapAbi.RuntimeIdentifier)</RuntimeIdentifier>
<DestinationSubPath>%(_TrimmableTypeMapAbi.Identity)/%(_TypeMapDlls.Filename)%(_TypeMapDlls.Extension)</DestinationSubPath>
<DestinationSubDirectory>%(_TrimmableTypeMapAbi.Identity)/</DestinationSubDirectory>
</_TypeMapDlls>
</ItemGroup>
<ItemGroup Condition=" '@(_LinkedTypeMapDlls->Count())' == '0' ">
<_BuildApkResolvedUserAssemblies Include="@(_TypeMapDlls)">
<Abi>%(_TypeMapDlls.Abi)</Abi>
<RuntimeIdentifier>%(_TypeMapDlls.RuntimeIdentifier)</RuntimeIdentifier>
<DestinationSubPath>%(_TypeMapDlls.DestinationSubPath)</DestinationSubPath>
<DestinationSubDirectory>%(_TypeMapDlls.DestinationSubDirectory)</DestinationSubDirectory>
</_BuildApkResolvedUserAssemblies>
</ItemGroup>
<Target Name="_UseReadyToRunTrimmableTypeMapAssembliesForPublish"
Condition=" '$(RuntimeIdentifier)' != '' and '$(PublishReadyToRun)' == 'true' "
AfterTargets="CreateReadyToRunImages">
<ItemGroup>
<_LinkedTypeMapDlls Remove="@(_LinkedTypeMapDlls)" />
<_TypeMapDlls Remove="@(_TypeMapDlls)" />
<_TrimmableTypeMapAbi Remove="@(_TrimmableTypeMapAbi)" />
<_TrimmableTypeMapLinkedAssemblies Include="$(IntermediateOutputPath)linked\_*.TypeMap.dll;$(IntermediateOutputPath)linked\_Microsoft.Android.TypeMap*.dll" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 MSBuild targets — The _TrimmableTypeMapLinkedAssemblies glob pattern ($(IntermediateOutputPath)linked\_*.TypeMap.dll;...) is duplicated verbatim in both _AddTrimmableTypeMapToResolvedFileToPublish (line 36) and here. Since MSBuild item groups are target-scoped, this duplication is functionally necessary, but could you extract the glob strings into a property (e.g. _TrimmableTypeMapLinkedGlob) to keep them in sync? If one changes without the other, the Remove won't match and you'd get duplicate entries in ResolvedFileToPublish.

Rule: Don't duplicate item group transforms

<_TrimmableTypeMapReadyToRunAssemblies Include="$(IntermediateOutputPath)R2R\_*.TypeMap.dll;$(IntermediateOutputPath)R2R\_Microsoft.Android.TypeMap*.dll" />
<ResolvedFileToPublish Remove="@(_TrimmableTypeMapLinkedAssemblies)" />
<ResolvedFileToPublish Remove="@(_TrimmableTypeMapReadyToRunAssemblies)" />
<ResolvedFileToPublish Include="@(_TrimmableTypeMapReadyToRunAssemblies)">
<RelativePath>%(Filename)%(Extension)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<RuntimeIdentifier>$(RuntimeIdentifier)</RuntimeIdentifier>
</ResolvedFileToPublish>
Comment on lines +48 to +60
</ItemGroup>
</Target>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text.RegularExpressions;
using NUnit.Framework;
using Xamarin.Android.Tasks;
using Xamarin.Android.Tools;
using Xamarin.ProjectTools;

namespace Xamarin.Android.Build.Tests {
Expand Down Expand Up @@ -197,6 +198,62 @@ public void CoreClrTrimmableTypeMap_PackagesJavaProxyThrowable ()
$"`{dexFile}` should include `android.runtime.JavaProxyThrowable`.");
}

[Test]
public void CoreClrTrimmableTypeMap_PackagesReadyToRunTypeMap ()
{
if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: true)) {
return;
}

var proj = new XamarinAndroidApplicationProject {
IsRelease = true,
};
proj.SetRuntime (AndroidRuntime.CoreCLR);
proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable");
proj.SetProperty ("RuntimeIdentifier", "android-arm64");
proj.SetProperty ("AndroidEnableAssemblyCompression", "false");

using var builder = CreateApkBuilder ();
Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");

var r2rTypeMap = builder.Output.GetIntermediaryPath (Path.Combine ("android-arm64", "R2R", "_Microsoft.Android.TypeMaps.dll"));
FileAssert.Exists (r2rTypeMap, "ReadyToRun should compile the generated TypeMap entry assembly.");
using (var r2rStream = File.OpenRead (r2rTypeMap)) {
using var r2rReader = new System.Reflection.PortableExecutable.PEReader (r2rStream);
Assert.IsTrue (
r2rReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory.Size > 0,
"ReadyToRun output for _Microsoft.Android.TypeMaps.dll should have a managed native header.");
}

var apk = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath, "android-arm64", $"{proj.PackageName}-Signed.apk");
FileAssert.Exists (apk);

var helper = new ArchiveAssemblyHelper (apk, useAssemblyStores: true);
var packagedTypeMapEntries = helper.ListArchiveContents ("lib/", arch: AndroidTargetArch.Arm64)
.Where (entry => entry.StartsWith ("lib/arm64-v8a/lib__", StringComparison.Ordinal) &&
entry.EndsWith (".dll.so", StringComparison.Ordinal) &&
!entry.EndsWith (".ni.dll.so", StringComparison.Ordinal) &&
entry.Contains ("TypeMap", StringComparison.Ordinal))
.ToArray ();
Assert.AreEqual (
packagedTypeMapEntries.Distinct ().Count (),
packagedTypeMapEntries.Length,
"TypeMap assemblies should be packaged only once; do not include both linked IL and ReadyToRun copies.");
Assert.AreEqual (
1,
packagedTypeMapEntries.Count (entry => entry == "lib/arm64-v8a/lib__Microsoft.Android.TypeMaps.dll.so"),
"_Microsoft.Android.TypeMaps.dll should be packaged only once.");

Assert.IsTrue (helper.Exists ("assemblies/arm64-v8a/_Microsoft.Android.TypeMaps.dll"), "_Microsoft.Android.TypeMaps.dll should exist in the APK.");
using (var packagedTypeMap = helper.ReadEntry ("assemblies/arm64-v8a/_Microsoft.Android.TypeMaps.dll", AndroidTargetArch.Arm64)) {
Assert.IsNotNull (packagedTypeMap, "_Microsoft.Android.TypeMaps.dll should be readable from the APK.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Testing — After Assert.IsNotNull, the nullable flow analysis still considers packagedTypeMap potentially null. Per repo conventions, avoid relying on assert-then-use patterns for null safety. Consider extracting to a local:

var stream = helper.ReadEntry ("assemblies/arm64-v8a/_Microsoft.Android.TypeMaps.dll", AndroidTargetArch.Arm64);
Assert.IsNotNull (stream, "...");
using var packagedReader = new System.Reflection.PortableExecutable.PEReader (stream);

This makes the non-null guarantee explicit and avoids a nested using block.

Rule: Test assertions must be specific

using var packagedReader = new System.Reflection.PortableExecutable.PEReader (packagedTypeMap);
Assert.IsTrue (
packagedReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory.Size > 0,
"Packaged _Microsoft.Android.TypeMaps.dll should be the ReadyToRun image, not the linked IL image.");
}
}

[Test]
public void TrimmableTypeMap_PreserveLists_ArePackagedInSdk ()
{
Expand Down