diff --git a/.github/skills/tests/SKILL.md b/.github/skills/tests/SKILL.md index 43b9072ae72..abb34aa1299 100644 --- a/.github/skills/tests/SKILL.md +++ b/.github/skills/tests/SKILL.md @@ -41,6 +41,7 @@ Classify the user's request: | `src/Mono.Android/Xamarin.Android.Net/` | On-device: networking tests in `Mono.Android.NET-Tests.csproj`. Host: `CheckClientHandlerTypeTests`, `LinkerTests` | | `src/Mono.Android/Android.*/**` | On-device: corresponding namespace tests in `Mono.Android.NET-Tests.csproj` | | `src/Xamarin.Android.Build.Tasks/Tasks/` | Host: task tests in `Xamarin.Android.Build.Tests.dll` (filter by task name) | +| `src/Microsoft.Android.Build.Tasks/Tasks/` | Standalone: `Microsoft.Android.Build.Tasks.Tests.csproj` | | `src/Xamarin.Android.Build.Tasks/Utilities/` | Host: `Xamarin.Android.Build.Tests.dll` — grep for test classes referencing the utility | | `src/Xamarin.Android.Build.Tasks/**/*.targets` | Host: `Xamarin.Android.Build.Tests.dll`. Device: `MSBuildDeviceIntegration.dll` | | `src/Microsoft.Android.Sdk.TrimmableTypeMap/` | Standalone: `TrimmableTypeMap.Tests.csproj`. Full-build: `TrimmableTypeMap.IntegrationTests` | diff --git a/.github/skills/tests/references/test-catalog.md b/.github/skills/tests/references/test-catalog.md index ded64ccbd2a..6847cab63fd 100644 --- a/.github/skills/tests/references/test-catalog.md +++ b/.github/skills/tests/references/test-catalog.md @@ -22,6 +22,7 @@ These tests can be run immediately with `dotnet test` on the `.csproj`, even if |-----------|---------|---------| | **assembly store reader** | `.github/skills/read-assembly-store/tests/AssemblyStore.Tests/` | `dotnet test .github/skills/read-assembly-store/tests/AssemblyStore.Tests/AssemblyStore.Tests.csproj -v minimal` | | **trimmable type map** (unit) | `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` | `dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj -v minimal` | +| **modern Android build tasks** | `src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/` | `dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -v minimal` | | **aidl** | `tests/Xamarin.Android.Tools.Aidl-Tests/` | `dotnet test tests/Xamarin.Android.Tools.Aidl-Tests/Xamarin.Android.Tools.Aidl-Tests.csproj -v minimal` | | **source writer** | `external/Java.Interop/tests/Xamarin.SourceWriter-Tests/` | `dotnet test external/Java.Interop/tests/Xamarin.SourceWriter-Tests/Xamarin.SourceWriter-Tests.csproj -v minimal` | | **java source** | `external/Java.Interop/tests/Java.Interop.Tools.JavaSource-Tests/` | `dotnet test external/Java.Interop/tests/Java.Interop.Tools.JavaSource-Tests/Java.Interop.Tools.JavaSource-Tests.csproj -v minimal` | diff --git a/Xamarin.Android.Build.Tasks.slnx b/Xamarin.Android.Build.Tasks.slnx index af35653f7fa..b323dd936bc 100644 --- a/Xamarin.Android.Build.Tasks.slnx +++ b/Xamarin.Android.Build.Tasks.slnx @@ -1,5 +1,6 @@ + diff --git a/Xamarin.Android.slnx b/Xamarin.Android.slnx index 0a1dd0bd1a3..2d6a0faadc4 100644 --- a/Xamarin.Android.slnx +++ b/Xamarin.Android.slnx @@ -57,6 +57,7 @@ + diff --git a/build-tools/automation/yaml-templates/build-windows-steps.yaml b/build-tools/automation/yaml-templates/build-windows-steps.yaml index 3ca5e4c334b..5c0a4d7c657 100644 --- a/build-tools/automation/yaml-templates/build-windows-steps.yaml +++ b/build-tools/automation/yaml-templates/build-windows-steps.yaml @@ -105,6 +105,21 @@ steps: testResultsFiles: "$(Agent.TempDirectory)/trimmable-typemap-tests/*.trx" testRunTitle: Microsoft.Android.Sdk.TrimmableTypeMap.Tests +- template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self + parameters: + command: test + project: src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj + arguments: -c $(XA.Build.Configuration) --logger trx --results-directory $(Agent.TempDirectory)/microsoft-android-build-tasks-tests + displayName: Test Microsoft.Android.Build.Tasks.Tests $(XA.Build.Configuration) + +- task: PublishTestResults@2 + displayName: publish Microsoft.Android.Build.Tasks.Tests results + condition: always() + inputs: + testResultsFormat: VSTest + testResultsFiles: "$(Agent.TempDirectory)/microsoft-android-build-tasks-tests/*.trx" + testRunTitle: Microsoft.Android.Build.Tasks.Tests + - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self parameters: command: test diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index 4f4e1110a01..34e09d2dfab 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -102,10 +102,18 @@ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)AndroidBuildConfig.pdb" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Tasks.dll" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Tasks.pdb" /> - <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Build.Tasks.dll" /> - <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Build.Tasks.pdb" /> - <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Sdk.TrimmableTypeMap.dll" /> - <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Sdk.TrimmableTypeMap.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\Microsoft.Android.Build.Tasks.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\Microsoft.Android.Build.Tasks.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\Microsoft.Android.Sdk.TrimmableTypeMap.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\Microsoft.Android.Sdk.TrimmableTypeMap.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\System.IO.Hashing.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\Microsoft.Android.Build.BaseTasks.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\Microsoft.Android.Build.BaseTasks.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\Java.Interop.Localization.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)net\Java.Interop.Localization.pdb" /> + <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)net\%(Identity)\Microsoft.Android.Build.BaseTasks.resources.dll')" /> + <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)net\%(Identity)\Microsoft.Android.Build.Tasks.resources.dll')" /> + <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)net\%(Identity)\Java.Interop.Localization.resources.dll')" /> <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)%(Identity)\Microsoft.Android.Build.BaseTasks.resources.dll')" /> <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)%(Identity)\Xamarin.Android.Build.Tasks.resources.dll')" /> <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)%(Identity)\Xamarin.Android.Tools.AndroidSdk.resources.dll')" /> diff --git a/src/Microsoft.Android.Build.Tasks/Microsoft.Android.Build.Tasks.csproj b/src/Microsoft.Android.Build.Tasks/Microsoft.Android.Build.Tasks.csproj index e40cbdc93b9..54c391ef59c 100644 --- a/src/Microsoft.Android.Build.Tasks/Microsoft.Android.Build.Tasks.csproj +++ b/src/Microsoft.Android.Build.Tasks/Microsoft.Android.Build.Tasks.csproj @@ -5,7 +5,7 @@ This assembly hosts MSBuild tasks that require .NET (net11.0) APIs that are not available in netstandard2.0, so they cannot live in Xamarin.Android.Build.Tasks.dll (which multi-targets netstandard2.0 to run on both .NET Framework and .NET MSBuild). Tasks here are imported with - so they run out-of-process on .NET. + so they run on .NET. Keep this assembly small and self-contained: link only the minimal helpers it needs. --> @@ -13,22 +13,44 @@ Microsoft.Android.Tasks enable Nullable - $(MicrosoftAndroidSdkOutDir) + + $(MicrosoftAndroidSdkOutDir)net\ false true ..\..\product.snk - + false + + + + + + + + + + + + + + + - - Xamarin.Android.Tasks.Properties.Resources + + Xamarin.Android.Tasks.Properties.%(Filename) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMissingTypeMapStubs.cs b/src/Microsoft.Android.Build.Tasks/Tasks/GenerateMissingTypeMapStubs.cs similarity index 99% rename from src/Xamarin.Android.Build.Tasks/Tasks/GenerateMissingTypeMapStubs.cs rename to src/Microsoft.Android.Build.Tasks/Tasks/GenerateMissingTypeMapStubs.cs index 3dfbc4fb2c6..64a524a8e5c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMissingTypeMapStubs.cs +++ b/src/Microsoft.Android.Build.Tasks/Tasks/GenerateMissingTypeMapStubs.cs @@ -7,7 +7,7 @@ using Microsoft.Android.Build.Tasks; using Microsoft.Android.Sdk.TrimmableTypeMap; -namespace Xamarin.Android.Tasks; +namespace Microsoft.Android.Tasks; /// /// Emits empty stub assemblies for per-assembly typemaps (_X.TypeMap.dll) that the root diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Microsoft.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs similarity index 93% rename from src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs rename to src/Microsoft.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs index c2d9fc52f11..abbc2f3e578 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs +++ b/src/Microsoft.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection.Metadata; @@ -10,9 +11,10 @@ using Microsoft.Android.Sdk.TrimmableTypeMap; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -using Xamarin.Android.Tools; +using Xamarin.Android.Tasks; +using Properties = Xamarin.Android.Tasks.Properties; -namespace Xamarin.Android.Tasks; +namespace Microsoft.Android.Tasks; public class GenerateTrimmableTypeMap : AndroidTask { @@ -82,9 +84,9 @@ public void LogMissingBaseConstructorError (string managedTypeName, string jniSi public void LogInvalidSuperArgumentsStringError (string managedTypeName, string superArgumentsString) => log.LogCodedError ("XA4262", Properties.Resources.XA4262, managedTypeName, superArgumentsString); public void LogCustomJavaObjectError (string managedTypeName) => - log.LogError ("{0}", $"XA4212: {string.Format (Properties.Resources.XA4212, managedTypeName)}"); + log.LogError ("{0}", $"XA4212: {string.Format (CultureInfo.CurrentCulture, Properties.Resources.XA4212, managedTypeName)}"); public void LogCustomJavaObjectWarning (string managedTypeName) => - log.LogWarning ("{0}", $"XA4212: {string.Format (Properties.Resources.XA4212, managedTypeName)}"); + log.LogWarning ("{0}", $"XA4212: {string.Format (CultureInfo.CurrentCulture, Properties.Resources.XA4212, managedTypeName)}"); } public override string TaskPrefix => "GTT"; @@ -241,7 +243,7 @@ public override bool RunTask () } IReadOnlyCollection? customViewTypeNames = CustomViewMapFile.IsNullOrEmpty () ? null - : MonoAndroidHelper.LoadCustomViewMapFile (BuildEngine4, CustomViewMapFile).Keys; + : LoadCustomViewTypeNames (CustomViewMapFile); result = generator.Execute ( assemblies, @@ -377,7 +379,29 @@ void WriteTypeMapFingerprints (IReadOnlyDictionary fingerprints) static bool IsFrameworkAssemblyItem (ITaskItem item) => string.Equals (item.GetMetadata ("FrameworkAssembly"), bool.TrueString, StringComparison.OrdinalIgnoreCase) || - MonoAndroidHelper.IsFrameworkAssembly (item); + string.Equals (item.GetMetadata ("FrameworkReferenceName"), "Microsoft.Android", StringComparison.Ordinal) || + item.GetMetadata ("FrameworkReferenceName").StartsWith ("Microsoft.NETCore.", StringComparison.OrdinalIgnoreCase) || + item.GetMetadata ("NuGetPackageId").StartsWith ("Microsoft.NETCore.App.Runtime.", StringComparison.OrdinalIgnoreCase) || + item.GetMetadata ("NuGetPackageId").StartsWith ("Microsoft.Android.Runtime.", StringComparison.OrdinalIgnoreCase); + + internal static IReadOnlyCollection LoadCustomViewTypeNames (string mapFile) + { + var typeNames = new HashSet (StringComparer.Ordinal); + if (!File.Exists (mapFile)) { + return typeNames; + } + foreach (var line in File.ReadLines (mapFile)) { + if (line.IsNullOrWhiteSpace ()) { + continue; + } + int separator = line.IndexOf (';'); + if (separator <= 0 || separator == line.Length - 1) { + throw new InvalidDataException ($"Invalid custom view map entry '{line}' in '{mapFile}'."); + } + typeNames.Add (line.Substring (0, separator)); + } + return typeNames; + } void WriteGeneratedAssembliesListFile (IReadOnlyList assemblies) { @@ -506,7 +530,7 @@ ITaskItem [] DeleteStaleJavaSources (IReadOnlyCollection generatedJav TaskItem CreateDeletedJavaItem (string fullPath) { var item = new TaskItem (fullPath); - item.SetMetadata ("RelativePath", PathUtil.GetRelativePath (JavaSourceOutputDirectory, fullPath)); + item.SetMetadata ("RelativePath", Path.GetRelativePath (JavaSourceOutputDirectory, fullPath)); return item; } @@ -533,7 +557,7 @@ static string GenerateApplicationRegistrationJava (IReadOnlyList registr sb.AppendLine ("\tpublic static void registerApplications ()"); sb.AppendLine ("\t{"); foreach (var javaClassName in registrationTypes) { - sb.AppendLine ($"\t\tmono.android.Runtime.registerNatives ({javaClassName}.class);"); + sb.AppendLine (CultureInfo.InvariantCulture, $"\t\tmono.android.Runtime.registerNatives ({javaClassName}.class);"); } sb.AppendLine ("\t}"); sb.AppendLine ("}"); diff --git a/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/BaseTest.cs b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/BaseTest.cs new file mode 100644 index 00000000000..e925283dfba --- /dev/null +++ b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/BaseTest.cs @@ -0,0 +1,31 @@ +using System.IO; +using System.Linq; +using NUnit.Framework; + +namespace Xamarin.Android.Build.Tests; + +public class BaseTest +{ + static readonly char [] InvalidChars = ['{', '}', '(', ')', '$', ':', ';', '"', '\'', ',', '=', '|']; + + public string Root => Path.Combine (TestContext.CurrentContext.WorkDirectory, "Microsoft.Android.Build.Tasks.Tests"); + + public string TestName { + get { + var result = TestContext.CurrentContext.Test.Name; + foreach (var c in InvalidChars.Concat (Path.GetInvalidPathChars ()).Concat (Path.GetInvalidFileNameChars ())) { + result = result.Replace (c, '_'); + } + return result.Replace ("_", ""); + } + } + + [TearDown] + public void Cleanup () + { + var testDirectory = Path.Combine (Root, "temp", TestName); + if (Directory.Exists (testDirectory)) { + Directory.Delete (testDirectory, recursive: true); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateMissingTypeMapStubsTests.cs b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateMissingTypeMapStubsTests.cs similarity index 99% rename from src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateMissingTypeMapStubsTests.cs rename to src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateMissingTypeMapStubsTests.cs index 0033d14a502..7d8e9bb3566 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateMissingTypeMapStubsTests.cs +++ b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateMissingTypeMapStubsTests.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; +using Microsoft.Android.Tasks; using Microsoft.Build.Framework; using NUnit.Framework; using Xamarin.Android.Tasks; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateTrimmableTypeMapTests.cs similarity index 72% rename from src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs rename to src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateTrimmableTypeMapTests.cs index 197e6dd0c7e..283dc2cf301 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs +++ b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateTrimmableTypeMapTests.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Android.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using NUnit.Framework; -using Xamarin.Android.Tasks; -using Xamarin.ProjectTools; namespace Xamarin.Android.Build.Tests { [TestFixture] @@ -47,6 +46,32 @@ public void Execute_InvalidTargetFrameworkVersion_Fails () Assert.IsNotEmpty (errors, "Should have logged an error."); } + [Test] + public void LoadCustomViewTypeNames_ParsesKeysAndIgnoresBlankLines () + { + var directory = Path.Combine (Root, "temp", TestName); + var mapFile = Path.Combine (directory, "custom-view-map.txt"); + Directory.CreateDirectory (directory); + File.WriteAllText (mapFile, "Example.View;example.View\n\nExample.View;example.OtherView\nOther.View;other.View\n"); + + var typeNames = GenerateTrimmableTypeMap.LoadCustomViewTypeNames (mapFile); + + CollectionAssert.AreEquivalent (new [] { "Example.View", "Other.View" }, typeNames); + } + + [Test] + public void LoadCustomViewTypeNames_InvalidEntryThrows () + { + var directory = Path.Combine (Root, "temp", TestName); + var mapFile = Path.Combine (directory, "custom-view-map.txt"); + Directory.CreateDirectory (directory); + File.WriteAllText (mapFile, "invalid"); + + var exception = Assert.Throws (() => GenerateTrimmableTypeMap.LoadCustomViewTypeNames (mapFile)); + + StringAssert.Contains ("Invalid custom view map entry 'invalid'", exception?.Message); + } + [Test] public void Execute_WithMonoAndroid_ProducesOutputs () { @@ -341,125 +366,6 @@ public void Execute_ManifestPlaceholdersAreResolvedForRooting () Assert.IsFalse (warnings.Any (w => w.Code == "XA4250"), "Resolved placeholder-based manifest references should not log XA4250."); } - [Test] - public void Execute_GenerateNativeAotProguardConfiguration_UsesDgmlTypeMetadata () - { - var path = Path.Combine (Root, "temp", TestName); - var dgmlFile = Path.Combine (path, "app.scan.dgml.xml"); - var acwMapFile = Path.Combine (path, "acw-map.txt"); - var outputFile = Path.Combine (path, "proguard", "proguard_project_references.cfg"); - Directory.CreateDirectory (path); - File.WriteAllText (dgmlFile, """ - - - - - - - - - - - - - - """); - File.WriteAllText (acwMapFile, """ - UnnamedProject.MainActivity, UnnamedProject;crc64a1.MainActivity - Android.App.Activity, Mono.Android;android.app.Activity - Duplicate.Type, My.Assembly;my.app.Duplicate - AndroidX.Activity.Result.Contract.ActivityResultContracts+TakePicture, Xamarin.AndroidX.Activity;androidx.activity.result.contract.ActivityResultContracts$TakePicture - Duplicate.Type;wrong.Duplicate - Other.Type;other.Type - """); - - var task = new GenerateNativeAotProguardConfiguration { - BuildEngine = new MockBuildEngine (TestContext.Out), - NativeAotDgmlFiles = new [] { new TaskItem (dgmlFile) }, - AcwMapFile = acwMapFile, - OutputFile = outputFile, - TrimJavaCallableWrappers = true, - }; - - Assert.IsTrue (task.Execute (), "Task should succeed."); - var proguard = File.ReadAllText (outputFile); - StringAssert.Contains ("-keep class crc64a1.MainActivity { *; }", proguard); - StringAssert.Contains ("-keep class android.app.Activity { *; }", proguard); - StringAssert.Contains ("-keep class my.app.Duplicate { *; }", proguard); - StringAssert.Contains ("-keep class androidx.activity.result.contract.ActivityResultContracts$TakePicture { *; }", proguard); - StringAssert.DoesNotContain ("wrong.Duplicate", proguard); - StringAssert.DoesNotContain ("other.Type", proguard); - } - - [Test] - public void Execute_GenerateNativeAotProguardConfiguration_KeepsAllWhenTrimmingDisabled () - { - var path = Path.Combine (Root, "temp", TestName); - var acwMapFile = Path.Combine (path, "acw-map.txt"); - var outputFile = Path.Combine (path, "proguard", "proguard_project_references.cfg"); - Directory.CreateDirectory (path); - File.WriteAllText (acwMapFile, """ - UnnamedProject.MainActivity, UnnamedProject;crc64a1.MainActivity - Android.App.Activity, Mono.Android;android.app.Activity - Duplicate.Type, My.Assembly;my.app.Duplicate - Other.Type;other.Type - """); - - // No DGML is provided: with trimming disabled the task must keep every ACW from the map - // rather than shrinking to the DGML-retained subset. - var task = new GenerateNativeAotProguardConfiguration { - BuildEngine = new MockBuildEngine (TestContext.Out), - AcwMapFile = acwMapFile, - OutputFile = outputFile, - TrimJavaCallableWrappers = false, - }; - - Assert.IsTrue (task.Execute (), "Task should succeed without a DGML when trimming is disabled."); - var proguard = File.ReadAllText (outputFile); - StringAssert.Contains ("-keep class crc64a1.MainActivity { *; }", proguard); - StringAssert.Contains ("-keep class android.app.Activity { *; }", proguard); - StringAssert.Contains ("-keep class my.app.Duplicate { *; }", proguard); - StringAssert.Contains ("-keep class other.Type { *; }", proguard); - } - - [Test] - public void Execute_GenerateNativeAotProguardConfiguration_IgnoresDgmlWhenTrimmingDisabled () - { - var path = Path.Combine (Root, "temp", TestName); - var dgmlFile = Path.Combine (path, "app.scan.dgml.xml"); - var acwMapFile = Path.Combine (path, "acw-map.txt"); - var outputFile = Path.Combine (path, "proguard", "proguard_project_references.cfg"); - Directory.CreateDirectory (path); - File.WriteAllText (dgmlFile, """ - - - - - - - - """); - File.WriteAllText (acwMapFile, """ - UnnamedProject.MainActivity, UnnamedProject;crc64a1.MainActivity - Android.App.Activity, Mono.Android;android.app.Activity - Other.Type;other.Type - """); - - var task = new GenerateNativeAotProguardConfiguration { - BuildEngine = new MockBuildEngine (TestContext.Out), - NativeAotDgmlFiles = new [] { new TaskItem (dgmlFile) }, - AcwMapFile = acwMapFile, - OutputFile = outputFile, - TrimJavaCallableWrappers = false, - }; - - Assert.IsTrue (task.Execute (), "Task should succeed and ignore the DGML when trimming is disabled."); - var proguard = File.ReadAllText (outputFile); - StringAssert.Contains ("-keep class crc64a1.MainActivity { *; }", proguard); - StringAssert.Contains ("-keep class android.app.Activity { *; }", proguard); - StringAssert.Contains ("-keep class other.Type { *; }", proguard); - } - GenerateTrimmableTypeMap CreateTask (ITaskItem [] assemblies, string outputDir, string javaDir, IList? messages = null, IList? warnings = null, IList? errors = null, string tfv = "v11.0") @@ -476,12 +382,16 @@ GenerateTrimmableTypeMap CreateTask (ITaskItem [] assemblies, string outputDir, static ITaskItem? FindMonoAndroidDll () { - var frameworkDir = TestEnvironment.MonoAndroidFrameworkDirectory; - if (string.IsNullOrEmpty (frameworkDir) || !Directory.Exists (frameworkDir)) { + var repositoryRoot = Path.GetFullPath (Path.Combine (AppContext.BaseDirectory, "..", "..", "..")); + var binDirectory = Path.Combine (repositoryRoot, "bin"); + if (!Directory.Exists (binDirectory)) { return null; } - var path = Path.Combine (frameworkDir, "Mono.Android.dll"); - if (!File.Exists (path)) { + var path = Directory.EnumerateFiles (binDirectory, "Mono.Android.dll", SearchOption.AllDirectories) + .FirstOrDefault (candidate => + candidate.Contains ($"{Path.DirectorySeparatorChar}Microsoft.Android.Ref.", StringComparison.Ordinal) && + candidate.Contains ($"{Path.DirectorySeparatorChar}ref{Path.DirectorySeparatorChar}", StringComparison.Ordinal)); + if (path is null) { return null; } var item = new TaskItem (path); diff --git a/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj new file mode 100644 index 00000000000..8e92f0383b8 --- /dev/null +++ b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj @@ -0,0 +1,24 @@ + + + + + + $(DotNetTargetFramework) + ..\..\..\..\bin\Test$(Configuration) + false + true + ..\..\..\..\product.snk + annotations + + + + + + + + + + + + + diff --git a/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/ResourcesTests.cs b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/ResourcesTests.cs new file mode 100644 index 00000000000..539215166e5 --- /dev/null +++ b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/ResourcesTests.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using System.Resources; +using NUnit.Framework; + +namespace Xamarin.Android.Build.Tests; + +[TestFixture] +public class ResourcesTests +{ + // The tasks in this assembly report XA#### diagnostics through resources linked from + // Xamarin.Android.Build.Tasks, and [Export] diagnostics through Java.Interop.Localization. + // Both must keep resolving their satellite assemblies after the move to this assembly. + // Assert that a translation exists and differs from English rather than asserting on the + // translated text itself, which the localization pipeline regenerates regularly. + [TestCase ("de")] + [TestCase ("ja")] + public void TaskDiagnosticsAreLocalized (string culture) + { + AssertLocalized (Xamarin.Android.Tasks.Properties.Resources.ResourceManager, "XA4250", culture); + AssertLocalized (Java.Interop.Localization.Resources.ResourceManager, "JavaCallableWrappers_XA4205", culture); + } + + static void AssertLocalized (ResourceManager resourceManager, string name, string culture) + { + var neutral = resourceManager.GetString (name, CultureInfo.InvariantCulture); + Assert.IsNotNull (neutral, $"'{name}' should exist in the neutral resources."); + + var translated = resourceManager.GetString (name, CultureInfo.GetCultureInfo (culture)); + Assert.IsNotNull (translated, $"'{name}' should exist in the '{culture}' resources."); + Assert.AreNotEqual (neutral, translated, + $"'{name}' fell back to English, so the '{culture}' satellite assembly was not found."); + } +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj index 2a08e9c073f..f194f868819 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj @@ -2,7 +2,7 @@ - $(TargetFrameworkNETStandard) + $(DotNetTargetFramework) enable Nullable true @@ -13,12 +13,10 @@ - - diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/README.md b/src/Microsoft.Android.Sdk.TrimmableTypeMap/README.md index 7d1cefdef8b..600ba82cb39 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/README.md +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/README.md @@ -185,5 +185,5 @@ builds), the generator runs exactly once per outer build. | `Microsoft.Android.Sdk.TypeMap.Trimmable.targets` | Shared pipeline: generation, Java stubs, packaging hookup, incremental `FileWrites`. | | `Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets` | CoreCLR specifics, incl. the post-trim `linked-java` regeneration. | | `Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets` | NativeAOT specifics (ILC inputs, proguard). | -| `Tasks/GenerateTrimmableTypeMap.cs` | The MSBuild task front-end for the generator. | -| `Microsoft.Android.Sdk.TrimmableTypeMap/**` | The generator/scanner library invoked by the task. | +| `../Microsoft.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs` | The net11.0 MSBuild task front-end for the generator. | +| `Microsoft.Android.Sdk.TrimmableTypeMap/**` | The net11.0 generator/scanner library invoked by the task. | diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaAnnotationParser.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaAnnotationParser.cs index 8ce19000586..6641250f0b0 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaAnnotationParser.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaAnnotationParser.cs @@ -127,11 +127,12 @@ static IReadOnlyDictionary GetJavaPropertyNames (TypeDefinition index.AssemblyName, rawTypeKind: 0 ); - if (!assemblies.TryGetValue (typeReference.AssemblyName, out attributeIndex) || - !attributeIndex.TypesByFullName.TryGetValue (typeReference.ManagedTypeName, out var resolvedHandle)) { + if (!assemblies.TryGetValue (typeReference.AssemblyName, out var resolvedIndex) || resolvedIndex is null || + !resolvedIndex.TypesByFullName.TryGetValue (typeReference.ManagedTypeName, out var resolvedHandle)) { annotationTypes [key] = null; return null; } + attributeIndex = resolvedIndex; attributeType = attributeIndex.Reader.GetTypeDefinition (resolvedHandle); } else { annotationTypes [key] = null; diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs index 78229072a2e..e09eb4b212a 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs @@ -68,9 +68,10 @@ bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHan if (resolvedIndex.TypesByFullName.TryGetValue (typeName, out handle)) { return true; } - if (!resolvedIndex.ForwardedTypeAssemblies.TryGetValue (typeName, out assemblyName)) { + if (!resolvedIndex.ForwardedTypeAssemblies.TryGetValue (typeName, out var forwardedAssemblyName) || forwardedAssemblyName is null) { break; } + assemblyName = forwardedAssemblyName; } handle = default; resolvedIndex = null; diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets index b12f80fdc8f..5dd6a30bb97 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets @@ -1,7 +1,8 @@ - + + <_TrimmableRuntimeProviderJavaName Condition=" '$(_TrimmableRuntimeProviderJavaName)' == '' ">mono.MonoRuntimeProvider diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets index d8f3645cf1d..5787482cd32 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets @@ -1,7 +1,8 @@ - + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.targets index 9b21a3c5777..ac248037e63 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.targets @@ -5,6 +5,7 @@ Android <_XamarinAndroidBuildTasksAssembly>..\tools\Xamarin.Android.Build.Tasks.dll + <_MicrosoftAndroidBuildTasksAssembly>..\tools\net\Microsoft.Android.Build.Tasks.dll true true diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateNativeAotProguardConfigurationTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateNativeAotProguardConfigurationTests.cs new file mode 100644 index 00000000000..fd9679e8c27 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateNativeAotProguardConfigurationTests.cs @@ -0,0 +1,128 @@ +using System.IO; +using Microsoft.Build.Utilities; +using NUnit.Framework; +using Xamarin.Android.Tasks; + +namespace Xamarin.Android.Build.Tests; + +[TestFixture] +[Parallelizable (ParallelScope.Children)] +public class GenerateNativeAotProguardConfigurationTests : BaseTest +{ + [Test] + public void Execute_UsesDgmlTypeMetadata () + { + var path = Path.Combine (Root, "temp", TestName); + var dgmlFile = Path.Combine (path, "app.scan.dgml.xml"); + var acwMapFile = Path.Combine (path, "acw-map.txt"); + var outputFile = Path.Combine (path, "proguard", "proguard_project_references.cfg"); + Directory.CreateDirectory (path); + File.WriteAllText (dgmlFile, """ + + + + + + + + + + + + + + """); + File.WriteAllText (acwMapFile, """ + UnnamedProject.MainActivity, UnnamedProject;crc64a1.MainActivity + Android.App.Activity, Mono.Android;android.app.Activity + Duplicate.Type, My.Assembly;my.app.Duplicate + AndroidX.Activity.Result.Contract.ActivityResultContracts+TakePicture, Xamarin.AndroidX.Activity;androidx.activity.result.contract.ActivityResultContracts$TakePicture + Duplicate.Type;wrong.Duplicate + Other.Type;other.Type + """); + + var task = new GenerateNativeAotProguardConfiguration { + BuildEngine = new MockBuildEngine (TestContext.Out), + NativeAotDgmlFiles = new [] { new TaskItem (dgmlFile) }, + AcwMapFile = acwMapFile, + OutputFile = outputFile, + TrimJavaCallableWrappers = true, + }; + + Assert.IsTrue (task.Execute (), "Task should succeed."); + var proguard = File.ReadAllText (outputFile); + StringAssert.Contains ("-keep class crc64a1.MainActivity { *; }", proguard); + StringAssert.Contains ("-keep class android.app.Activity { *; }", proguard); + StringAssert.Contains ("-keep class my.app.Duplicate { *; }", proguard); + StringAssert.Contains ("-keep class androidx.activity.result.contract.ActivityResultContracts$TakePicture { *; }", proguard); + StringAssert.DoesNotContain ("wrong.Duplicate", proguard); + StringAssert.DoesNotContain ("other.Type", proguard); + } + + [Test] + public void Execute_KeepsAllWhenTrimmingDisabled () + { + var path = Path.Combine (Root, "temp", TestName); + var acwMapFile = Path.Combine (path, "acw-map.txt"); + var outputFile = Path.Combine (path, "proguard", "proguard_project_references.cfg"); + Directory.CreateDirectory (path); + File.WriteAllText (acwMapFile, """ + UnnamedProject.MainActivity, UnnamedProject;crc64a1.MainActivity + Android.App.Activity, Mono.Android;android.app.Activity + Duplicate.Type, My.Assembly;my.app.Duplicate + Other.Type;other.Type + """); + + var task = new GenerateNativeAotProguardConfiguration { + BuildEngine = new MockBuildEngine (TestContext.Out), + AcwMapFile = acwMapFile, + OutputFile = outputFile, + TrimJavaCallableWrappers = false, + }; + + Assert.IsTrue (task.Execute (), "Task should succeed without a DGML when trimming is disabled."); + var proguard = File.ReadAllText (outputFile); + StringAssert.Contains ("-keep class crc64a1.MainActivity { *; }", proguard); + StringAssert.Contains ("-keep class android.app.Activity { *; }", proguard); + StringAssert.Contains ("-keep class my.app.Duplicate { *; }", proguard); + StringAssert.Contains ("-keep class other.Type { *; }", proguard); + } + + [Test] + public void Execute_IgnoresDgmlWhenTrimmingDisabled () + { + var path = Path.Combine (Root, "temp", TestName); + var dgmlFile = Path.Combine (path, "app.scan.dgml.xml"); + var acwMapFile = Path.Combine (path, "acw-map.txt"); + var outputFile = Path.Combine (path, "proguard", "proguard_project_references.cfg"); + Directory.CreateDirectory (path); + File.WriteAllText (dgmlFile, """ + + + + + + + + """); + File.WriteAllText (acwMapFile, """ + UnnamedProject.MainActivity, UnnamedProject;crc64a1.MainActivity + Android.App.Activity, Mono.Android;android.app.Activity + Other.Type;other.Type + """); + + var task = new GenerateNativeAotProguardConfiguration { + BuildEngine = new MockBuildEngine (TestContext.Out), + NativeAotDgmlFiles = new [] { new TaskItem (dgmlFile) }, + AcwMapFile = acwMapFile, + OutputFile = outputFile, + TrimJavaCallableWrappers = false, + }; + + Assert.IsTrue (task.Execute (), "Task should succeed and ignore the DGML when trimming is disabled."); + var proguard = File.ReadAllText (outputFile); + StringAssert.Contains ("-keep class crc64a1.MainActivity { *; }", proguard); + StringAssert.Contains ("-keep class android.app.Activity { *; }", proguard); + StringAssert.Contains ("-keep class other.Type { *; }", proguard); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index 2be97ec0043..eeb02950445 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -31,6 +31,7 @@ + @@ -237,7 +238,6 @@ - diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index f47ada6c422..c181279b1ba 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -44,8 +44,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - - + + diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj index 7b6fadca1fd..bb8aa7c1a02 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj @@ -1,7 +1,7 @@ - $(DotNetStableTargetFramework) + $(DotNetTargetFramework) latest enable false