From b8b7b3db08d2da1ba9912e11d5a74135a23ab61a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 27 Oct 2026 09:37:54 -0500 Subject: [PATCH] [Microsoft.Android.Build.Tasks] Enable MT for package names Run in the MSBuild process during -mt builds instead of sending its calls to a separate task process. Use the multithreaded base and declare the task attribute directly. Resolve manifest reads through TaskEnvironment without changing public inputs or package selection. Add focused path and project-isolation tests while keeping the existing Java identifier checks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GetAndroidPackageName.cs | 20 +++--- .../GetAndroidPackageNameTests.cs | 66 +++++++++++++++++++ 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Android.Build.Tasks/Tasks/GetAndroidPackageName.cs b/src/Microsoft.Android.Build.Tasks/Tasks/GetAndroidPackageName.cs index d4c6ddebde4..535a3066f24 100644 --- a/src/Microsoft.Android.Build.Tasks/Tasks/GetAndroidPackageName.cs +++ b/src/Microsoft.Android.Build.Tasks/Tasks/GetAndroidPackageName.cs @@ -36,7 +36,8 @@ namespace Microsoft.Android.Tasks { - public class GetAndroidPackageName : AndroidTask + [MSBuildMultiThreadableTask] + public class GetAndroidPackageName : MultithreadedAndroidTask { public override string TaskPrefix => "GAP"; @@ -52,13 +53,16 @@ public class GetAndroidPackageName : AndroidTask public override bool RunTask () { - if (!ManifestFile.IsNullOrEmpty () && File.Exists (ManifestFile)) { - using var stream = File.OpenRead (ManifestFile); - using var reader = XmlReader.Create (stream); - if (reader.MoveToContent () == XmlNodeType.Element) { - var package = reader.GetAttribute ("package"); - if (!package.IsNullOrEmpty ()) { - PackageName = ManifestPlaceholderResolver.Replace (ManifestPlaceholders, package); + if (!ManifestFile.IsNullOrEmpty ()) { + var manifestFile = TaskEnvironment.GetAbsolutePath (ManifestFile); + if (File.Exists (manifestFile)) { + using var stream = File.OpenRead (manifestFile); + using var reader = XmlReader.Create (stream); + if (reader.MoveToContent () == XmlNodeType.Element) { + var package = reader.GetAttribute ("package"); + if (!package.IsNullOrEmpty ()) { + PackageName = ManifestPlaceholderResolver.Replace (ManifestPlaceholders, package); + } } } } diff --git a/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GetAndroidPackageNameTests.cs b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GetAndroidPackageNameTests.cs index 2d805af152e..b1e199b0cd4 100644 --- a/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GetAndroidPackageNameTests.cs +++ b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GetAndroidPackageNameTests.cs @@ -1,4 +1,7 @@ +using System; using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; using Microsoft.Build.Framework; using NUnit.Framework; using Microsoft.Android.Tasks; @@ -9,6 +12,69 @@ namespace Xamarin.Android.Build.Tests { [Parallelizable (ParallelScope.Children)] public class GetAndroidPackageNameTests { + [TestCase (false, true, "com.application.id", "com.example.some_name")] + [TestCase (true, true, null, "com.example.some_name")] + [TestCase (false, false, null, "Example.Example")] + [TestCase (true, false, "com.application.id", "com.application.id")] + public void ManifestPaths (bool absolutePath, bool exists, string? packageName, string expected) + { + string directory = CreateProjectDirectory (); + try { + string path = Path.Combine (directory, "AndroidManifest.xml"); + if (exists) { + File.WriteAllText (path, ""); + } + string manifestFile = absolutePath ? path : "AndroidManifest.xml"; + var task = new GetAndroidPackageName { + BuildEngine = new MockBuildEngine (TestContext.Out), + TaskEnvironment = TaskEnvironment.CreateWithProjectDirectoryAndEnvironment (directory, new Dictionary ()), + ManifestFile = manifestFile, + AssemblyName = "Example", + PackageName = packageName, + ManifestPlaceholders = ["prefix=com.example"], + }; + + Assert.IsTrue (task.Execute ()); + Assert.AreEqual (expected, task.PackageName); + Assert.AreEqual (manifestFile, task.ManifestFile, "Rooting file I/O must not change the public input."); + } finally { + Directory.Delete (directory, recursive: true); + } + } + + [Test] + public void ConcurrentProjectDirectories () + { + string root = CreateProjectDirectory (); + try { + Parallel.For (0, 2, i => { + string directory = Path.Combine (root, $"project{i}"); + string package = $"com.project{i}"; + Directory.CreateDirectory (directory); + File.WriteAllText (Path.Combine (directory, "AndroidManifest.xml"), $""); + var task = new GetAndroidPackageName { + BuildEngine = new MockBuildEngine (TextWriter.Null), + TaskEnvironment = TaskEnvironment.CreateWithProjectDirectoryAndEnvironment (directory, new Dictionary ()), + ManifestFile = "AndroidManifest.xml", + AssemblyName = "Fallback", + }; + + Assert.IsTrue (task.Execute ()); + Assert.AreEqual (package, task.PackageName); + }); + } finally { + Directory.Delete (root, recursive: true); + } + } + + static string CreateProjectDirectory () + { + string directory = Path.Combine (TestContext.CurrentContext.WorkDirectory, "GetAndroidPackageName", Guid.NewGuid ().ToString ("N")); + Directory.CreateDirectory (directory); + Assert.AreNotEqual (Directory.GetCurrentDirectory (), directory); + return directory; + } + [TestCase ("com.example.for", "for")] [TestCase ("com.class.example", "class")] [TestCase ("com.example.true", "true")]