Skip to content

Commit de0f450

Browse files
committed
Merge branch 'main' into all-assemblies-per-rid
* main: [Xamarin.Android.Build.Tasks] per-RID assemblies & typemaps (dotnet#8164) [AndroidDependenciesTests] Test both manifest types (dotnet#8186)
2 parents 61bf387 + 6836818 commit de0f450

File tree

4 files changed

+167
-36
lines changed

4 files changed

+167
-36
lines changed

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

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ void LogRunMode (string mode)
242242
var assemblies = new T ();
243243
bool hasExportReference = false;
244244
bool haveMonoAndroid = false;
245-
246245
foreach (ITaskItem assembly in ResolvedAssemblies) {
247246
bool value;
248247
if (bool.TryParse (assembly.GetMetadata (AndroidSkipJavaStubGeneration), out value) && value) {
@@ -590,5 +589,137 @@ void WriteTypeMappings (AndroidTargetArch targetArch, List<JavaType> types, Type
590589
}
591590
GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray ();
592591
}
592+
593+
/// <summary>
594+
/// <para>
595+
/// Classifier will see only unique assemblies, since that's what's processed by the JI type scanner - even though some assemblies may have
596+
/// abi-specific features (e.g. inlined `IntPtr.Size` or processor-specific intrinsics), the **types** and **methods** will all be the same and, thus,
597+
/// there's no point in scanning all of the additional copies of the same assembly.
598+
/// </para>
599+
/// <para>
600+
/// This, however, doesn't work for the rewriter which needs to rewrite all of the copies so that they all have the same generated wrappers. In
601+
/// order to do that, we need to go over the list of assemblies found by the classifier, see if they are abi-specific ones and then add all the
602+
/// marshal methods from the abi-specific assembly copies, so that the rewriter can easily rewrite them all.
603+
/// </para>
604+
/// <para>
605+
/// This method returns a dictionary matching `AssemblyDefinition` instances to the path on disk to the assembly file they were loaded from. It is necessary
606+
/// because <see cref="LoadAssembly"/> uses a stream to load the data, in order to avoid later sharing violation issues when writing the assemblies. Path
607+
/// information is required by <see cref="MarshalMethodsAssemblyRewriter"/> to be available for each <see cref="MarshalMethodEntry"/>
608+
/// </para>
609+
/// </summary>
610+
Dictionary<AssemblyDefinition, string> AddMethodsFromAbiSpecificAssemblies (MarshalMethodsClassifier classifier, XAAssemblyResolver resolver, Dictionary<string, List<ITaskItem>> abiSpecificAssemblies)
611+
{
612+
IDictionary<string, IList<MarshalMethodEntry>> marshalMethods = classifier.MarshalMethods;
613+
ICollection<AssemblyDefinition> assemblies = classifier.Assemblies;
614+
var newAssemblies = new List<AssemblyDefinition> ();
615+
var assemblyPaths = new Dictionary<AssemblyDefinition, string> ();
616+
617+
foreach (AssemblyDefinition asmdef in assemblies) {
618+
string fileName = Path.GetFileName (asmdef.MainModule.FileName);
619+
if (!abiSpecificAssemblies.TryGetValue (fileName, out List<ITaskItem>? abiAssemblyItems)) {
620+
continue;
621+
}
622+
623+
List<MarshalMethodEntry> assemblyMarshalMethods = FindMarshalMethodsForAssembly (marshalMethods, asmdef);;
624+
Log.LogDebugMessage ($"Assembly {fileName} is ABI-specific");
625+
foreach (ITaskItem abiAssemblyItem in abiAssemblyItems) {
626+
if (String.Compare (abiAssemblyItem.ItemSpec, asmdef.MainModule.FileName, StringComparison.Ordinal) == 0) {
627+
continue;
628+
}
629+
630+
Log.LogDebugMessage ($"Looking for matching mashal methods in {abiAssemblyItem.ItemSpec}");
631+
FindMatchingMethodsInAssembly (abiAssemblyItem, classifier, assemblyMarshalMethods, resolver, newAssemblies, assemblyPaths);
632+
}
633+
}
634+
635+
if (newAssemblies.Count > 0) {
636+
foreach (AssemblyDefinition asmdef in newAssemblies) {
637+
assemblies.Add (asmdef);
638+
}
639+
}
640+
641+
return assemblyPaths;
642+
}
643+
644+
List<MarshalMethodEntry> FindMarshalMethodsForAssembly (IDictionary<string, IList<MarshalMethodEntry>> marshalMethods, AssemblyDefinition asm)
645+
{
646+
var seenNativeCallbacks = new HashSet<MethodDefinition> ();
647+
var assemblyMarshalMethods = new List<MarshalMethodEntry> ();
648+
649+
foreach (var kvp in marshalMethods) {
650+
foreach (MarshalMethodEntry method in kvp.Value) {
651+
if (method.NativeCallback.Module.Assembly != asm) {
652+
continue;
653+
}
654+
655+
// More than one overriden method can use the same native callback method, we're interested only in unique native
656+
// callbacks, since that's what gets rewritten.
657+
if (seenNativeCallbacks.Contains (method.NativeCallback)) {
658+
continue;
659+
}
660+
661+
seenNativeCallbacks.Add (method.NativeCallback);
662+
assemblyMarshalMethods.Add (method);
663+
}
664+
}
665+
666+
return assemblyMarshalMethods;
667+
}
668+
669+
void FindMatchingMethodsInAssembly (ITaskItem assemblyItem, MarshalMethodsClassifier classifier, List<MarshalMethodEntry> assemblyMarshalMethods, XAAssemblyResolver resolver, List<AssemblyDefinition> newAssemblies, Dictionary<AssemblyDefinition, string> assemblyPaths)
670+
{
671+
AssemblyDefinition asm = LoadAssembly (assemblyItem.ItemSpec, resolver);
672+
newAssemblies.Add (asm);
673+
assemblyPaths.Add (asm, assemblyItem.ItemSpec);
674+
675+
foreach (MarshalMethodEntry methodEntry in assemblyMarshalMethods) {
676+
TypeDefinition wantedType = methodEntry.NativeCallback.DeclaringType;
677+
TypeDefinition? type = asm.MainModule.FindType (wantedType.FullName);
678+
if (type == null) {
679+
throw new InvalidOperationException ($"Internal error: type '{wantedType.FullName}' not found in assembly '{assemblyItem.ItemSpec}', a linker error?");
680+
}
681+
682+
if (type.MetadataToken != wantedType.MetadataToken) {
683+
throw new InvalidOperationException ($"Internal error: type '{type.FullName}' in assembly '{assemblyItem.ItemSpec}' has a different token ID than the original type");
684+
}
685+
686+
FindMatchingMethodInType (methodEntry, type, classifier);
687+
}
688+
}
689+
690+
void FindMatchingMethodInType (MarshalMethodEntry methodEntry, TypeDefinition type, MarshalMethodsClassifier classifier)
691+
{
692+
string callbackName = methodEntry.NativeCallback.FullName;
693+
694+
foreach (MethodDefinition typeNativeCallbackMethod in type.Methods) {
695+
if (String.Compare (typeNativeCallbackMethod.FullName, callbackName, StringComparison.Ordinal) != 0) {
696+
continue;
697+
}
698+
699+
if (typeNativeCallbackMethod.Parameters.Count != methodEntry.NativeCallback.Parameters.Count) {
700+
continue;
701+
}
702+
703+
if (typeNativeCallbackMethod.MetadataToken != methodEntry.NativeCallback.MetadataToken) {
704+
throw new InvalidOperationException ($"Internal error: tokens don't match for '{typeNativeCallbackMethod.FullName}'");
705+
}
706+
707+
bool allMatch = true;
708+
for (int i = 0; i < typeNativeCallbackMethod.Parameters.Count; i++) {
709+
if (String.Compare (typeNativeCallbackMethod.Parameters[i].ParameterType.FullName, methodEntry.NativeCallback.Parameters[i].ParameterType.FullName, StringComparison.Ordinal) != 0) {
710+
allMatch = false;
711+
break;
712+
}
713+
}
714+
715+
if (!allMatch) {
716+
continue;
717+
}
718+
719+
Log.LogDebugMessage ($"Found match for '{typeNativeCallbackMethod.FullName}' in {type.Module.FileName}");
720+
string methodKey = classifier.GetStoreMethodKey (methodEntry);
721+
classifier.MarshalMethods[methodKey].Add (new MarshalMethodEntry (methodEntry, typeNativeCallbackMethod));
722+
}
723+
}
593724
}
594725
}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidDependenciesTests.cs

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Text;
56
using System.Xml;
67
using System.Xml.Linq;
78
using NUnit.Framework;
@@ -16,11 +17,9 @@ public class AndroidDependenciesTests : BaseTest
1617
{
1718
[Test]
1819
[NonParallelizable] // Do not run environment modifying tests in parallel.
19-
public void InstallAndroidDependenciesTest ()
20+
public void InstallAndroidDependenciesTest ([Values ("GoogleV2", "Xamarin")] string manifestType)
2021
{
2122
AssertCommercialBuild ();
22-
// We need to grab the latest API level *before* changing env vars
23-
var apiLevel = AndroidSdkResolver.GetMaxInstalledPlatform ();
2423
var oldSdkPath = Environment.GetEnvironmentVariable ("TEST_ANDROID_SDK_PATH");
2524
var oldJdkPath = Environment.GetEnvironmentVariable ("TEST_ANDROID_JDK_PATH");
2625
try {
@@ -33,41 +32,48 @@ public void InstallAndroidDependenciesTest ()
3332
Directory.Delete (path, recursive: true);
3433
Directory.CreateDirectory (path);
3534
}
36-
var proj = new XamarinAndroidApplicationProject {
37-
TargetSdkVersion = apiLevel.ToString (),
35+
36+
var proj = new XamarinAndroidApplicationProject ();
37+
var buildArgs = new List<string> {
38+
"AcceptAndroidSDKLicenses=true",
39+
$"AndroidManifestType={manifestType}",
3840
};
39-
const string ExpectedPlatformToolsVersion = "34.0.4";
41+
// When using the default Xamarin manifest, this test should fail if we can't install any of the defaults in Xamarin.Android.Tools.Versions.props
42+
// When using the Google manifest, override the platform tools version to the one in their manifest as it only ever contains one version
43+
if (manifestType == "GoogleV2") {
44+
buildArgs.Add ($"AndroidSdkPlatformToolsVersion={GetCurrentPlatformToolsVersion ()}");
45+
}
46+
4047
using (var b = CreateApkBuilder ()) {
4148
b.CleanupAfterSuccessfulBuild = false;
4249
string defaultTarget = b.Target;
4350
b.Target = "InstallAndroidDependencies";
4451
b.BuildLogFile = "install-deps.log";
45-
Assert.IsTrue (b.Build (proj, parameters: new string [] {
46-
"AcceptAndroidSDKLicenses=true",
47-
"AndroidManifestType=GoogleV2", // Need GoogleV2 so we can install API-32
48-
$"AndroidSdkPlatformToolsVersion={ExpectedPlatformToolsVersion}",
49-
}), "InstallAndroidDependencies should have succeeded.");
50-
b.Target = defaultTarget;
51-
b.BuildLogFile = "build.log";
52-
Assert.IsTrue (b.Build (proj, true), "build should have succeeded.");
53-
bool usedNewDir = b.LastBuildOutput.ContainsText ($"Output Property: _AndroidSdkDirectory={sdkPath}");
54-
if (!usedNewDir) {
55-
// Is this because the platform-tools version changed (again?!)
56-
try {
57-
var currentPlatformToolsVersion = GetCurrentPlatformToolsVersion ();
58-
if (currentPlatformToolsVersion != ExpectedPlatformToolsVersion) {
59-
Assert.Fail ($"_AndroidSdkDirectory not set to new SDK path `{sdkPath}`, *probably* because Google's repository has a newer platform-tools package! " +
60-
$"repository2-3.xml contains platform-tools {currentPlatformToolsVersion}; expected {ExpectedPlatformToolsVersion}!");
52+
Assert.IsTrue (b.Build (proj, parameters: buildArgs.ToArray ()), "InstallAndroidDependencies should have succeeded.");
53+
54+
// When dependencies can not be resolved/installed a warning will be present in build output:
55+
// Dependency `platform-tools` should have been installed but could not be resolved.
56+
var depFailedMessage = "should have been installed but could not be resolved";
57+
bool failedToInstall = b.LastBuildOutput.ContainsText (depFailedMessage);
58+
if (failedToInstall) {
59+
var sb = new StringBuilder ();
60+
foreach (var line in b.LastBuildOutput) {
61+
if (line.Contains (depFailedMessage)) {
62+
sb.AppendLine (line);
6163
}
6264
}
63-
catch (Exception e) {
64-
TestContext.WriteLine ($"Could not extract platform-tools version from repository2-3.xml: {e}");
65-
}
65+
Assert.Fail ($"A required dependency was not installed, warnings are listed below. Please check the task output in 'install-deps.log'.\n{sb.ToString ()}");
6666
}
67-
Assert.IsTrue (usedNewDir, $"_AndroidSdkDirectory was not set to new SDK path `{sdkPath}`.");
67+
68+
b.Target = defaultTarget;
69+
b.BuildLogFile = "build.log";
70+
Assert.IsTrue (b.Build (proj, true), "build should have succeeded.");
71+
Assert.IsTrue ( b.LastBuildOutput.ContainsText ($"Output Property: _AndroidSdkDirectory={sdkPath}"),
72+
$"_AndroidSdkDirectory was not set to new SDK path `{sdkPath}`. Please check the task output in 'install-deps.log'");
6873
Assert.IsTrue (b.LastBuildOutput.ContainsText ($"Output Property: _JavaSdkDirectory={jdkPath}"),
69-
$"_JavaSdkDirectory was not set to new JDK path `{jdkPath}`.");
70-
Assert.IsTrue (b.LastBuildOutput.ContainsText ($"JavaPlatformJarPath={sdkPath}"), $"JavaPlatformJarPath did not contain new SDK path `{sdkPath}`.");
74+
$"_JavaSdkDirectory was not set to new JDK path `{jdkPath}`. Please check the task output in 'install-deps.log'");
75+
Assert.IsTrue (b.LastBuildOutput.ContainsText ($"JavaPlatformJarPath={sdkPath}"),
76+
$"JavaPlatformJarPath did not contain new SDK path `{sdkPath}`. Please check the task output in 'install-deps.log'");
7177
}
7278
} finally {
7379
Environment.SetEnvironmentVariable ("TEST_ANDROID_SDK_PATH", oldSdkPath);
@@ -89,7 +95,7 @@ static string GetCurrentPlatformToolsVersion ()
8995

9096
var revision = platformToolsPackage.Element ("revision");
9197

92-
return $"{revision.Element ("major")}.{revision.Element ("minor")}.{revision.Element ("micro")}";
98+
return $"{revision.Element ("major")?.Value}.{revision.Element ("minor")?.Value}.{revision.Element ("micro")?.Value}";
9399
}
94100

95101
[Test]

src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ sealed class AssemblyImports
2727
TaskLoggingHelper log;
2828

2929
public MarshalMethodsAssemblyRewriter (IDictionary<string, IList<MarshalMethodEntry>> methods, ICollection<AssemblyDefinition> uniqueAssemblies, TaskLoggingHelper log)
30-
{
31-
throw new NotImplementedException ();
32-
}
33-
34-
public MarshalMethodsAssemblyRewriter (IDictionary<string, IList<MarshalMethodEntry>> methods, ICollection<AssemblyDefinition> uniqueAssemblies, IDictionary<AssemblyDefinition, string> assemblyPaths, TaskLoggingHelper log)
3530
{
3631
this.assemblyPaths = assemblyPaths;
3732
this.methods = methods ?? throw new ArgumentNullException (nameof (methods));

src/Xamarin.Android.Build.Tasks/Utilities/XAAssemblyResolver.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ public XAAssemblyResolver (TaskLoggingHelper log, bool loadDebugSymbols, ReaderP
119119

120120
public AssemblyDefinition? Resolve (AssemblyNameReference name, ReaderParameters? parameters)
121121
{
122-
Console.WriteLine ($"XAAssemblyResolver.Resolve (\"{name}\")");
123122
return Resolve (AndroidTargetArch.None, name, parameters);
124123
}
125124

0 commit comments

Comments
 (0)