Skip to content

Commit d80c9b1

Browse files
authored
[Xamarin.Android.Build.Tasks] Fix Assembly Ordering in PackageManager (#6057)
* [Xamarin.Android.Build.Tasks] Fix Assembly Ordering in PackageManager Context: * https://discord.com/channels/732297728826277939/732297965019988138/859449393472471082 * ...through... * https://discord.com/channels/732297728826277939/732297965019988138/859454102999400488 The ordering of `MonoPackageManager_Resources.Assemblies` is very important to process startup, e.g. see 9c04378, wherein we decide that we *must* preload the *first* assembly listed within `MonoPackageManager_Resources.Assemblies`, as that's The One that has the correct resource IDs. The problem is threefold: 1. The order of `@(_ResolvedUserAssemblies)` is poorly defined/undefined. 2. `@(_ResolvedUserAssemblies)` contains *paths* which includes directory names. 3. The observed ordering of `@(_ResolvedUserAssemblies)` *differs* between "legacy" MSBuild and `dotnet build`. See also: https://github.com/xamarin/xamarin-android/blob/52d6880e74337db3fa245ea82b2a02b5016efece/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs#L95-L109 The code in `GeneratePackageManagerJava.cs` line 97 was *always* faulty: `assemblies` contains *paths*, not filenames, and thus `fileNameEq (a.ItemSpec, mainFileName)` will *always* return false (e.g. `a.ItemSpec` would be `obj/Debug/whatever/MainAssembly.dll`, while `mainFileName` will be `MainAssembly.dll`, which will never be equal). It Just Happened™ to work on Legacy because `@(_ResolvedUserAssemblies)` *always* contained the main/"app" assembly as the first assembly. This isn't the case on .NET 6 `dotnet build`. The "giveaway" is the contents of `MonoPackageManager_Resources`: ```java public class MonoPackageManager_Resources { public static String[] Assemblies = new String[]{ /* We need to ensure that "HelloAndroid.dll" comes first in this list. */ "Xamarin.AndroidX.Activity.dll", // … }; } ``` `HelloAndroid.dll` isn't `Xamarin.AndroidX.Activity.dll`. So let's rework the `GeneratePackageManagerJava` to force the issue and always put the `MainAssembly` first in the list. We also upgrade the filter to use the filenames rather then the entire path when checking for the main assembly. Unit tests have also been added to ensure that the output is what we expect.
1 parent b7224e5 commit d80c9b1

File tree

5 files changed

+110
-8
lines changed

5 files changed

+110
-8
lines changed

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System;
44
using System.Collections.Generic;
55
using System.IO;
6-
using System.Linq;
76
using System.Reflection;
87
using System.Text;
98
using Microsoft.Build.Framework;
@@ -89,12 +88,7 @@ public override bool RunTask ()
8988
var doc = AndroidAppManifest.Load (Manifest, MonoAndroidHelper.SupportedVersions);
9089
int minApiVersion = doc.MinSdkVersion == null ? 4 : (int) doc.MinSdkVersion;
9190
// We need to include any special assemblies in the Assemblies list
92-
var assemblies = ResolvedUserAssemblies
93-
.Concat (MonoAndroidHelper.GetFrameworkAssembliesToTreatAsUserAssemblies (ResolvedAssemblies))
94-
.ToList ();
9591
var mainFileName = Path.GetFileName (MainAssembly);
96-
Func<string,string,bool> fileNameEq = (a,b) => a.Equals (b, StringComparison.OrdinalIgnoreCase);
97-
assemblies = assemblies.Where (a => fileNameEq (a.ItemSpec, mainFileName)).Concat (assemblies.Where (a => !fileNameEq (a.ItemSpec, mainFileName))).ToList ();
9892

9993
using (var pkgmgr = MemoryStreamPool.Shared.CreateStreamWriter ()) {
10094
pkgmgr.WriteLine ("package mono;");
@@ -104,7 +98,15 @@ public override bool RunTask ()
10498
pkgmgr.WriteLine ("\tpublic static String[] Assemblies = new String[]{");
10599

106100
pkgmgr.WriteLine ("\t\t/* We need to ensure that \"{0}\" comes first in this list. */", mainFileName);
107-
foreach (var assembly in assemblies) {
101+
pkgmgr.WriteLine ("\t\t\"" + mainFileName + "\",");
102+
foreach (var assembly in ResolvedUserAssemblies) {
103+
if (string.Compare (Path.GetFileName (assembly.ItemSpec), mainFileName, StringComparison.OrdinalIgnoreCase) == 0)
104+
continue;
105+
pkgmgr.WriteLine ("\t\t\"" + Path.GetFileName (assembly.ItemSpec) + "\",");
106+
}
107+
foreach (var assembly in MonoAndroidHelper.GetFrameworkAssembliesToTreatAsUserAssemblies (ResolvedAssemblies)) {
108+
if (string.Compare (Path.GetFileName (assembly.ItemSpec), mainFileName, StringComparison.OrdinalIgnoreCase) == 0)
109+
continue;
108110
pkgmgr.WriteLine ("\t\t\"" + Path.GetFileName (assembly.ItemSpec) + "\",");
109111
}
110112

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ public void DesignerBeforeNuGetRestore ([Values (true, false)] bool restoreInSin
323323
var packageManagerPath = Path.Combine (Root, appb.ProjectDirectory, proj.IntermediateOutputPath, "android", "src", "mono", "MonoPackageManager_Resources.java");
324324
var before = GetAssembliesFromPackageManager (packageManagerPath);
325325
if (!Builder.UseDotNet) {
326-
Assert.AreEqual ("", before, $"After first `{appb.Target}`, assemblies list would be empty.");
326+
Assert.AreEqual ("\"App1.dll\",", before, $"After first `{appb.Target}`, assemblies list should only have main App dll.");
327327
}
328328

329329
// NuGet restore, either with /t:Restore in a separate MSBuild call or /restore in a single call
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package mono;
2+
public class MonoPackageManager_Resources {
3+
public static String[] Assemblies = new String[]{
4+
/* We need to ensure that "HelloAndroid.dll" comes first in this list. */
5+
"HelloAndroid.dll",
6+
"Xamarin.AndroidX.SavedState.dll",
7+
};
8+
public static String[] Dependencies = new String[]{
9+
};
10+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using NUnit.Framework;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using Xamarin.Android.Tasks;
7+
using Microsoft.Build.Framework;
8+
using Microsoft.Build.Utilities;
9+
using Microsoft.Android.Build.Tasks;
10+
11+
namespace Xamarin.Android.Build.Tests
12+
{
13+
[TestFixture]
14+
[Category ("Node-2")]
15+
public class GeneratePackageManagerJavaTests : BaseTest
16+
{
17+
#pragma warning disable 414
18+
static object [] CheckPackageManagerAssemblyOrderChecks () => new object [] {
19+
new object[] {
20+
/* resolvedUserAssemblies */ new string [] {
21+
"linked/Xamarin.AndroidX.SavedState.dll",
22+
"linked/HelloAndroid.dll",
23+
},
24+
/* resolvedAssemblies */ new string [] {
25+
"linked/Xamarin.AndroidX.SavedState.dll",
26+
"linked/HelloAndroid.dll",
27+
"linked/System.Console.dll",
28+
"linked/System.Linq.dll",
29+
}
30+
},
31+
new object[] {
32+
/* resolvedUserAssemblies */ new string [] {
33+
"linked/HelloAndroid.dll",
34+
"linked/Xamarin.AndroidX.SavedState.dll",
35+
},
36+
/* resolvedAssemblies */ new string [] {
37+
"linked/Xamarin.AndroidX.SavedState.dll",
38+
"linked/System.Console.dll",
39+
"linked/System.Linq.dll",
40+
"linked/HelloAndroid.dll",
41+
}
42+
},
43+
};
44+
#pragma warning restore 414
45+
[Test]
46+
[TestCaseSource (nameof (CheckPackageManagerAssemblyOrderChecks))]
47+
public void CheckPackageManagerAssemblyOrder (string[] resolvedUserAssemblies, string[] resolvedAssemblies)
48+
{
49+
// avoid a PathTooLongException because using the TestName will include ALL the arguments.
50+
var testHash = Files.HashString (string.Join ("", resolvedUserAssemblies) + string.Join ("", resolvedAssemblies));
51+
var path = Path.Combine (Root, "temp", $"CheckPackageManagerAssemblyOrder{testHash}");
52+
Directory.CreateDirectory (path);
53+
54+
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), new [] {
55+
new ApiInfo { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true },
56+
new ApiInfo { Id = "28", Level = 28, Name = "Pie", FrameworkVersion = "v9.0", Stable = true },
57+
});
58+
MonoAndroidHelper.RefreshSupportedVersions (new [] {
59+
Path.Combine (referencePath, "MonoAndroid"),
60+
});
61+
62+
File.WriteAllText (Path.Combine (path, "AndroidManifest.xml"), $@"<?xml version='1.0' ?><manifest xmlns:android='http://schemas.android.com/apk/res/android' package='com.microsoft.net6.helloandroid' android:versionCode='1' />");
63+
64+
var resolvedUserAssembliesList = resolvedUserAssemblies.Select (x => new TaskItem (x));
65+
var resolvedAssembliesList = resolvedAssemblies.Select (x => new TaskItem (x));
66+
67+
var task = new GeneratePackageManagerJava {
68+
BuildEngine = new MockBuildEngine (TestContext.Out),
69+
ResolvedUserAssemblies = resolvedUserAssembliesList.ToArray (),
70+
ResolvedAssemblies = resolvedAssembliesList.ToArray (),
71+
OutputDirectory = Path.Combine(path, "src", "mono"),
72+
EnvironmentOutputDirectory = Path.Combine (path, "env"),
73+
MainAssembly = "linked/HelloAndroid.dll",
74+
TargetFrameworkVersion = "v6.0",
75+
Manifest = Path.Combine (path, "AndroidManifest.xml"),
76+
IsBundledApplication = false,
77+
SupportedAbis = new string [] { "x86" , "arm64-v8a" },
78+
AndroidPackageName = "com.microsoft.net6.helloandroid",
79+
EnablePreloadAssembliesDefault = false,
80+
InstantRunEnabled = false,
81+
};
82+
Assert.IsTrue (task.Execute (), "Task should have executed.");
83+
AssertFileContentsMatch (Path.Combine (Root, "Expected", "CheckPackageManagerAssemblyOrder.java"), Path.Combine(path, "src", "mono", "MonoPackageManager_Resources.java"));
84+
}
85+
}
86+
}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
<Link>..\Expected\GenerateDesignerFileWithLibraryReferenceExpected.cs</Link>
4242
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4343
</Content>
44+
<Content Include="Expected\CheckPackageManagerAssemblyOrder.java">
45+
<Link>..\Expected\CheckPackageManagerAssemblyOrder.java</Link>
46+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
47+
</Content>
4448
</ItemGroup>
4549

4650
<ItemGroup>

0 commit comments

Comments
 (0)