diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
index 2b056f02870..8d21f4d71ce 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
@@ -264,10 +264,13 @@ _ResolveAssemblies MSBuild target.
- <_ResolvedAssemblies Include="@(ResolvedAssemblies->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" Condition=" '%(DestinationSubPath)' != '' " />
- <_ResolvedUserAssemblies Include="@(ResolvedUserAssemblies->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" Condition=" '%(DestinationSubPath)' != '' " />
- <_ResolvedFrameworkAssemblies Include="@(ResolvedFrameworkAssemblies->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" Condition=" '%(DestinationSubPath)' != '' " />
- <_ResolvedSymbols Include="@(ResolvedSymbols->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" Condition=" '%(DestinationSubPath)' != '' " />
+ <_ResolvedAssemblies Include="@(_AndroidAssembliesToLinkNoShrink->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" Condition=" '%(DestinationSubPath)' != '' " />
+ <_ResolvedAssemblies Include="@(_AndroidAssembliesToPackageWithoutLinkNoShrink)" Condition=" '%(DestinationSubPath)' != '' " />
+ <_ResolvedUserAssemblies Include="@(ResolvedUserAssemblies->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" Condition=" '%(DestinationSubPath)' != '' " />
+ <_ResolvedFrameworkAssemblies Include="@(_AndroidAssembliesToLinkNoShrink->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" Condition=" '%(FrameworkAssembly)' == 'True' And '%(DestinationSubPath)' != '' " />
+ <_ResolvedFrameworkAssemblies Include="@(_AndroidAssembliesToPackageWithoutLinkNoShrink)" Condition=" '%(DestinationSubPath)' != '' " />
+ <_ResolvedSymbols Include="@(_AndroidSymbolsToLinkNoShrink->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" Condition=" '%(DestinationSubPath)' != '' " />
+ <_ResolvedSymbols Include="@(_AndroidSymbolsToPackageWithoutLinkNoShrink)" Condition=" '%(DestinationSubPath)' != '' " />
<_ShrunkAssemblies Include="@(_ResolvedAssemblies)" />
<_ShrunkUserAssemblies Include="@(_ResolvedUserAssemblies)" />
<_ShrunkFrameworkAssemblies Include="@(_ResolvedFrameworkAssemblies)" />
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
index a6e5abbc0ac..212009784dc 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
@@ -95,6 +95,9 @@ public override bool RunTask ()
throw new InvalidOperationException ($"Internal error: assembly '{sourceArch}' targets architecture '{sourceArch}', while destination assembly '{destination}' targets '{destinationArch}' instead");
}
+ if (TryProcessWithoutPipeline (source, destination))
+ continue;
+
// Each architecture must have a different set of context classes, or otherwise only the first instance of the assembly may be rewritten.
if (currentArch != sourceArch) {
currentArch = sourceArch;
@@ -128,6 +131,8 @@ public override bool RunTask ()
return !Log.HasLoggedErrors;
}
+ protected virtual bool TryProcessWithoutPipeline (ITaskItem source, ITaskItem destination) => false;
+
protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkContext context)
{
// FindJavaObjectsStep
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateACWMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateACWMap.cs
index 7d289d2663d..bfb161f7619 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateACWMap.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateACWMap.cs
@@ -38,7 +38,7 @@ void GenerateMap ()
var entries = new List ();
foreach (var assembly in singleArchAssemblies) {
- var wrappersPath = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (assembly.ItemSpec);
+ var wrappersPath = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (assembly);
if (!File.Exists (wrappersPath)) {
Log.LogError ($"'{wrappersPath}' not found.");
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaCallableWrappers.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaCallableWrappers.cs
index ca59e949238..0d47b346c49 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaCallableWrappers.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaCallableWrappers.cs
@@ -62,7 +62,7 @@ void GenerateWrappers (List assemblies)
var sw = Stopwatch.StartNew ();
foreach (var assembly in assemblies) {
- var wrappersPath = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (assembly.ItemSpec);
+ var wrappersPath = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (assembly);
if (!File.Exists (wrappersPath)) {
Log.LogError ($"'{wrappersPath}' not found.");
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
index cde9d9bc8f9..147d1e2fe18 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
@@ -1,6 +1,8 @@
#nullable enable
using System;
+using System.IO;
+using Microsoft.Build.Framework;
using MonoDroid.Tuner;
namespace Xamarin.Android.Tasks
@@ -17,6 +19,21 @@ public class LinkAssembliesNoShrink : AssemblyModifierPipeline
public bool UseDesignerAssembly { get; set; }
+ protected override bool TryProcessWithoutPipeline (ITaskItem source, ITaskItem destination)
+ {
+ if (!bool.TryParse (source.GetMetadata ("AndroidSkipAssemblyModification"), out bool skipAssemblyModification) || !skipAssemblyModification)
+ return false;
+
+ // Downstream scanners treat a zero-byte file as an assembly that did not need scanning.
+ var marker = Path.ChangeExtension (destination.ItemSpec, ".scan.empty");
+ var markerDirectory = Path.GetDirectoryName (marker);
+ if (markerDirectory.IsNullOrEmpty ())
+ throw new InvalidOperationException ($"Could not determine the output directory for '{marker}'.");
+ Directory.CreateDirectory (markerDirectory);
+ JavaObjectsXmlFile.WriteEmptyFile (marker, Log);
+ return true;
+ }
+
protected override void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkContext context)
{
// FixAbstractMethodsStep
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessAssemblies.cs
index 5c166e75935..c48e6f55836 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessAssemblies.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessAssemblies.cs
@@ -18,6 +18,7 @@ namespace Xamarin.Android.Tasks
/// Also sets some metadata:
/// * %(FrameworkAssembly)=True to determine if framework or user assembly
/// * %(HasMonoAndroidReference)=True for incremental build performance
+ /// * %(AndroidSkipAssemblyModification)=True for framework assemblies with no Android types
/// * Modify %(DestinationSubDirectory) and %(DestinationSubPath) if an assembly has an architecture-specific version
///
public class ProcessAssemblies : AndroidTask
@@ -138,11 +139,19 @@ void SetMetadataForAssemblies (List output, Dictionary "CaptureFrameworkAssemblies.targets") {
+ TextContent = () =>
+"""
+
+
+
+
+
+"""
+ };
+ var proj = new XamarinAndroidApplicationProject {
+ EmbedAssembliesIntoApk = true,
+ Imports = { captureFrameworkAssemblies },
+ };
+ proj.SetRuntime (AndroidRuntime.CoreCLR);
+ proj.SetProperty (KnownProperties.RuntimeIdentifier, runtimeIdentifier);
+
+ using var builder = CreateApkBuilder ();
+ Assert.IsTrue (builder.Build (proj), "first build should succeed");
+ builder.Output.AssertTargetIsNotSkipped ("_LinkAssembliesNoShrink");
+
+ string [] GetFrameworkAssemblyMetadata (string assemblyName)
+ {
+ string prefix = $"FrameworkAssembly={assemblyName}|";
+ string line = builder.LastBuildOutput.Single (line => line.Contains (prefix, StringComparison.Ordinal));
+ int start = line.IndexOf (prefix, StringComparison.Ordinal);
+ var metadata = line.Substring (start + "FrameworkAssembly=".Length).Split ('|');
+ Assert.AreEqual (5, metadata.Length, $"Unexpected framework assembly metadata: {line}");
+ return metadata;
+ }
+
+ var projectDirectory = Path.Combine (Root, builder.ProjectDirectory);
+ string ResolveProjectPath (string path) => Path.GetFullPath (Path.Combine (projectDirectory, path));
+
+ var assemblyDirectory = Path.Combine (projectDirectory, proj.IntermediateOutputPath, runtimeIdentifier, "android", "assets", abi);
+ var stagedMicrosoftCSharp = Path.Combine (assemblyDirectory, "Microsoft.CSharp.dll");
+ var marker = Path.Combine (assemblyDirectory, "Microsoft.CSharp.scan.empty");
+ var microsoftCSharp = GetFrameworkAssemblyMetadata ("Microsoft.CSharp");
+
+ FileAssert.Exists (ResolveProjectPath (microsoftCSharp [1]));
+ Assert.That (microsoftCSharp [2], Does.StartWith ("Microsoft.NETCore.App.Runtime."),
+ $"Microsoft.CSharp.dll should come from a runtime pack, but its package ID was '{microsoftCSharp [2]}'.");
+ Assert.AreNotEqual (Path.GetFullPath (stagedMicrosoftCSharp), ResolveProjectPath (microsoftCSharp [1]),
+ "Microsoft.CSharp.dll should be packaged directly from its runtime pack.");
+ FileAssert.DoesNotExist (stagedMicrosoftCSharp);
+ FileAssert.Exists (marker);
+ Assert.AreEqual (0, new FileInfo (marker).Length, $"{marker} should be empty.");
+ Assert.AreEqual (Path.GetFullPath (marker), ResolveProjectPath (microsoftCSharp [3]),
+ "JavaObjectsXmlFile should point to the shared scan marker.");
+ Assert.AreEqual (Path.GetFullPath (marker), ResolveProjectPath (microsoftCSharp [4]),
+ "TypeMapObjectsXmlFile should point to the shared scan marker.");
+
+ var monoAndroid = GetFrameworkAssemblyMetadata ("Mono.Android");
+ var stagedMonoAndroid = Path.Combine (assemblyDirectory, "Mono.Android.dll");
+ Assert.AreEqual (Path.GetFullPath (stagedMonoAndroid), ResolveProjectPath (monoAndroid [1]),
+ "Mono.Android.dll should continue to use the staged assembly.");
+ FileAssert.Exists (stagedMonoAndroid);
+ FileAssert.Exists (Path.ChangeExtension (stagedMonoAndroid, ".jlo.xml"));
+ FileAssert.Exists (Path.ChangeExtension (stagedMonoAndroid, ".typemap.xml"));
+ FileAssert.DoesNotExist (Path.Combine (assemblyDirectory, "Mono.Android.scan.empty"));
+
+ var outputDirectory = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath);
+ var apk = Directory.GetFiles (outputDirectory, "*-Signed.apk", SearchOption.AllDirectories).Single ();
+ var archive = new ArchiveAssemblyHelper (apk, useAssemblyStores: true);
+ Assert.IsTrue (archive.Exists ($"assemblies/{abi}/Microsoft.CSharp.dll"),
+ $"Microsoft.CSharp.dll should be packaged in {apk}.");
+
+ Assert.IsTrue (builder.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "second build should succeed");
+ builder.Output.AssertTargetIsSkipped ("_LinkAssembliesNoShrink");
+ }
+
[Test]
[Ignore ("Flaky timing-based test. Disabled while investigating incremental build regressions. See: https://github.com/dotnet/android/issues/11792")]
public void BasicApplicationRepetitiveBuild ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime)
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JavaObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JavaObjectsXmlFile.cs
index f0c1f62433a..d3e51243322 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/JavaObjectsXmlFile.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/JavaObjectsXmlFile.cs
@@ -8,6 +8,7 @@
using Java.Interop.Tools.JavaCallableWrappers.Adapters;
using Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers;
using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Xamarin.Android.Tasks;
@@ -91,6 +92,12 @@ void ExportACWMappingTypes (XmlWriter xml)
public static string GetJavaObjectsXmlFilePath (string assemblyPath)
=> Path.ChangeExtension (assemblyPath, ".jlo.xml");
+ public static string GetJavaObjectsXmlFilePath (ITaskItem assembly)
+ {
+ var path = assembly.GetMetadata ("JavaObjectsXmlFile");
+ return path.IsNullOrEmpty () ? GetJavaObjectsXmlFilePath (assembly.ItemSpec) : path;
+ }
+
public static JavaObjectsXmlFile Import (string filename, JavaObjectsXmlFileReadType readType)
{
// If the file has zero length, then the assembly wasn't scanned because it couldn't contain JLOs.
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs
index ece5fe1eba2..19180b34778 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs
@@ -338,7 +338,7 @@ public ReleaseGenerationState GetReleaseGenerationState ()
var adapter = new TypeMapObjectsFileAdapter (targetArch);
foreach (var assembly in assemblies) {
- var typeMapPath = TypeMapObjectsXmlFile.GetTypeMapObjectsXmlFilePath (assembly.ItemSpec);
+ var typeMapPath = TypeMapObjectsXmlFile.GetTypeMapObjectsXmlFilePath (assembly);
if (!File.Exists (typeMapPath)) {
log.LogError ($"'{typeMapPath}' not found.");
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs
index 0d38817c825..5a610af49d1 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs
@@ -4,6 +4,7 @@
using System.IO;
using System.Xml;
using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using ModuleReleaseData = Xamarin.Android.Tasks.TypeMapGenerator.ModuleReleaseData;
@@ -165,6 +166,12 @@ void ExportTypeMapReleaseEntry (XmlWriter xml, TypeMapReleaseEntry entry, string
public static string GetTypeMapObjectsXmlFilePath (string assemblyPath)
=> Path.ChangeExtension (assemblyPath, ".typemap.xml");
+ public static string GetTypeMapObjectsXmlFilePath (ITaskItem assembly)
+ {
+ var path = assembly.GetMetadata ("TypeMapObjectsXmlFile");
+ return path.IsNullOrEmpty () ? GetTypeMapObjectsXmlFilePath (assembly.ItemSpec) : path;
+ }
+
public static TypeMapObjectsXmlFile Import (string filename)
{
// If the file has zero length, then the assembly wasn't scanned because it couldn't contain JLOs.
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index f043a72abc5..572bab7b0b8 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -1434,6 +1434,31 @@ because xbuild doesn't support framework reference assemblies.
<_AllResolvedAssemblies Include="@(ResolvedAssemblies)" />
+
+
+ <_AndroidLinkNoShrinkOutput
+ Condition=" '%(ResolvedAssemblies.AndroidSkipAssemblyModification)' == 'True' "
+ >$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubDirectory)%(Filename).scan.empty
+ <_AndroidLinkNoShrinkOutput
+ Condition=" '%(ResolvedAssemblies.AndroidSkipAssemblyModification)' != 'True' "
+ >$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)
+
+ <_AndroidAssembliesToLinkNoShrink
+ Include="@(ResolvedAssemblies)"
+ Condition=" '%(ResolvedAssemblies.AndroidSkipAssemblyModification)' != 'True' " />
+ <_AndroidAssembliesToPackageWithoutLinkNoShrink
+ Include="@(ResolvedAssemblies)"
+ Condition=" '%(ResolvedAssemblies.AndroidSkipAssemblyModification)' == 'True' ">
+ $(MonoAndroidIntermediateAssemblyDir)%(DestinationSubDirectory)%(Filename).scan.empty
+ $(MonoAndroidIntermediateAssemblyDir)%(DestinationSubDirectory)%(Filename).scan.empty
+
+ <_AndroidSymbolsToLinkNoShrink
+ Include="@(ResolvedSymbols)"
+ Condition=" '%(ResolvedSymbols.AndroidSkipAssemblyModification)' != 'True' " />
+ <_AndroidSymbolsToPackageWithoutLinkNoShrink
+ Include="@(ResolvedSymbols)"
+ Condition=" '%(ResolvedSymbols.AndroidSkipAssemblyModification)' == 'True' " />
@@ -1441,7 +1466,7 @@ because xbuild doesn't support framework reference assemblies.
DependsOnTargets="_LinkAssembliesNoShrinkInputs"
Condition="'$(PublishTrimmed)' != 'true'"
Inputs="@(ResolvedAssemblies);$(_AndroidBuildPropertiesCache)"
- Outputs="@(ResolvedAssemblies->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')">
+ Outputs="@(ResolvedAssemblies->'%(_AndroidLinkNoShrinkOutput)')">