From 29e4d05d3f994e0cf4a884f9a9a523737a01cd56 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Thu, 12 Oct 2023 09:40:03 -1000 Subject: [PATCH 01/12] =?UTF-8?q?=EF=BB=BF[Xamarin.Android.Build.Tasks]=20?= =?UTF-8?q?Add=20support=20for=20@(AndroidMavenLibrary).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../installers/create-installers.targets | 2 + .../Xamarin.Android.Bindings.Maven.targets | 35 +++ .../Microsoft.Android.Sdk.After.targets | 1 + .../Tasks/MavenDownloadTask.cs | 207 ++++++++++++++++ .../Utilities/ITaskItemExtensions.cs | 27 +++ .../Utilities/MavenExtensions.cs | 229 ++++++++++++++++++ .../Xamarin.Android.Build.Tasks.csproj | 1 + .../Xamarin.Android.Build.Tasks.targets | 5 + 8 files changed, 507 insertions(+) create mode 100644 src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index db38390c930..68ed22efabf 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -110,6 +110,7 @@ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libZipSharp.dll" /> <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)%(Identity)\libZipSharp.resources.dll')" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libZipSharp.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)MavenNet.dll" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.Unix.dll" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.Unix.pdb" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Build.BaseTasks.dll" /> @@ -136,6 +137,7 @@ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Application.targets" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.ClassParse.targets" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.Core.targets" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.Maven.targets" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Tasks.dll" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Tasks.pdb" /> <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)%(Identity)\Microsoft.Android.Build.BaseTasks.resources.dll')" /> diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets new file mode 100644 index 00000000000..3469be99d53 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets @@ -0,0 +1,35 @@ + + + + + + + + + $(HOME)\Library\Caches\MavenCacheDirectory\ + $(LocalAppData)\MavenCacheDirectory\ + $(MavenCacheDirectory)\ + + + + + + + + + + + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets index 662aff0130b..95063269a7b 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets @@ -17,6 +17,7 @@ This file is imported *after* the Microsoft.NET.Sdk/Sdk.targets. + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs new file mode 100644 index 00000000000..300085f95b3 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MavenNet; +using MavenNet.Models; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using MonoDroid.Utils; + +namespace Xamarin.Android.Tasks; + +public class MavenDownloadTask : AndroidAsyncTask +{ + public override string TaskPrefix => "MDT"; + + /// + /// The cache directory to use for Maven artifacts. + /// + [Required] + public string MavenCacheDirectory { get; set; } = null!; // NRT enforced by [Required] + + /// + /// The set of Maven libraries that we are being asked to acquire. + /// + public ITaskItem []? AndroidMavenLibraries { get; set; } + + /// + /// The set of requested Maven libraries that we were able to successfully download. + /// + [Output] + public ITaskItem []? ResolvedAndroidMavenLibraries { get; set; } + + public async override System.Threading.Tasks.Task RunTaskAsync () + { + var resolved = new List (); + + // Note each called function is responsible for raising any errors it encounters to the user + foreach (var library in AndroidMavenLibraries.OrEmpty ()) { + + // Validate artifact + var id = library.ItemSpec; + var version = library.GetRequiredMetadata ("Version", Log); + + if (version is null) + continue; + + var artifact = MavenExtensions.ParseArtifact (id, version, Log); + + if (artifact is null) + continue; + + // Check for local files + if (GetLocalArtifactOrDefault (library, Log) is TaskItem cached_result) { + library.CopyMetadataTo (cached_result); + resolved.Add (cached_result); + continue; + } + + // Check for repository files + if (await GetRepositoryArtifactOrDefault (artifact, library, Log) is TaskItem result) { + library.CopyMetadataTo (result); + resolved.Add (result); + continue; + } + } + + ResolvedAndroidMavenLibraries = resolved.ToArray (); + } + + TaskItem? GetLocalArtifactOrDefault (ITaskItem item, TaskLoggingHelper log) + { + // Handles a Repository="file" entry, like: + // + var type = item.GetMetadataOrDefault ("Repository", "Central"); + + if (type.Equals ("file", StringComparison.InvariantCultureIgnoreCase)) { + var artifact_file = item.GetMetadataOrDefault ("PackageFile", string.Empty); + var pom_file = item.GetMetadataOrDefault ("PomFile", string.Empty); + + if (!artifact_file.HasValue () || !pom_file.HasValue ()) { + log.LogError ("'PackageFile' and 'PomFile' must be specified when using a 'File' repository."); + return null; + } + + if (!File.Exists (artifact_file)) { + log.LogError ("Specified package file '{0}' does not exist.", artifact_file); + return null; + } + + if (!File.Exists (pom_file)) { + log.LogError ("Specified pom file '{0}' does not exist.", pom_file); + return null; + } + + var result = new TaskItem (artifact_file); + + result.SetMetadata ("ArtifactSpec", item.ItemSpec); + result.SetMetadata ("ArtifactFile", artifact_file); + result.SetMetadata ("ArtifactPom", pom_file); + + return result; + } + + return null; + } + + async System.Threading.Tasks.Task GetRepositoryArtifactOrDefault (Artifact artifact, ITaskItem item, TaskLoggingHelper log) + { + // Handles a Repository="Central|Google|" entry, like: + // + // Note if Repository is not specifed, it is defaulted to "Central" + + // Initialize repo + var repository = GetRepository (item); + + if (repository is null) + return null; + + artifact.SetRepository (repository); + + // Download artifact + var artifact_file = await MavenExtensions.DownloadPayload (artifact, MavenCacheDirectory, Log); + + if (artifact_file is null) + return null; + + // Download POM + var pom_file = await MavenExtensions.DownloadPom (artifact, MavenCacheDirectory, Log); + + if (pom_file is null) + return null; + + var result = new TaskItem (artifact_file); + + result.SetMetadata ("ArtifactSpec", item.ItemSpec); + result.SetMetadata ("ArtifactFile", artifact_file); + result.SetMetadata ("ArtifactPom", pom_file); + + return result; + } + + async System.Threading.Tasks.Task TryGetParentPom (ITaskItem item, TaskLoggingHelper log) + { + var child_pom_file = item.GetRequiredMetadata ("ArtifactPom", Log); + + // Shouldn't be possible because we just created this items + if (child_pom_file is null) + return null; + + // No parent POM needed + if (!(MavenExtensions.CheckForNeededParentPom (child_pom_file) is Artifact artifact)) + return null; + + // Initialize repo (parent will be in same repository as child) + var repository = GetRepository (item); + + if (repository is null) + return null; + + artifact.SetRepository (repository); + + // Download POM + var pom_file = await MavenExtensions.DownloadPom (artifact, MavenCacheDirectory, Log); + + if (pom_file is null) + return null; + + var result = new TaskItem ($"{artifact.GroupId}:{artifact.Id}"); + + result.SetMetadata ("Version", artifact.Versions.FirstOrDefault ()); + result.SetMetadata ("ArtifactPom", pom_file); + + // Copy repository data + item.CopyMetadataTo (result); + + return result; + } + + MavenRepository? GetRepository (ITaskItem item) + { + var type = item.GetMetadataOrDefault ("Repository", "Central"); + + var repo = type.ToLowerInvariant () switch { + "central" => MavenRepository.FromMavenCentral (), + "google" => MavenRepository.FromGoogle (), + _ => (MavenRepository?) null + }; + + if (repo is null && type.StartsWith ("http", StringComparison.OrdinalIgnoreCase)) + repo = MavenRepository.FromUrl (type); + + if (repo is null) + Log.LogError ("Unknown Maven repository: '{0}'.", type); + + return repo; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs index c40a60e9bfc..a3f80305ac8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Xml.Linq; using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; namespace Xamarin.Android.Tasks { @@ -19,5 +21,30 @@ public static IEnumerable ToXElements (this ICollection ite yield return e; } } + + public static string GetMetadataOrDefault (this ITaskItem item, string name, string defaultValue) + { + var value = item.GetMetadata (name); + + if (string.IsNullOrWhiteSpace (value)) + return defaultValue; + + return value; + } + + public static string? GetRequiredMetadata (this ITaskItem item, string name, TaskLoggingHelper log) + { + var value = item.GetMetadata (name); + + if (string.IsNullOrWhiteSpace (value)) { + log.LogError ("Item is missing required metadata '{0}'", name); + return null; + } + + return value; + } + + public static bool HasMetadata (this ITaskItem item, string name) + => item.MetadataNames.OfType ().Contains (name, StringComparer.OrdinalIgnoreCase); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs new file mode 100644 index 00000000000..93921ff79cb --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs @@ -0,0 +1,229 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using MavenNet; +using MavenNet.Models; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tasks; + +static class MavenExtensions +{ + static readonly char [] separator = [':']; + + /// + /// Shortcut for !string.IsNullOrWhiteSpace (s) + /// + public static bool HasValue ([NotNullWhen (true)] this string? s) => !string.IsNullOrWhiteSpace (s); + + // Helps to 'foreach' into a possibly null array + public static T [] OrEmpty (this T []? value) + { + return value ?? Enumerable.Empty ().ToArray (); + } + + public static Artifact? ParseArtifact (string id, string version, TaskLoggingHelper log) + { + var parts = id.Split (separator, StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length != 2 || parts.Any (string.IsNullOrWhiteSpace)) { + log.LogError ("Artifact specification '{0}' is invalid.", id); + return null; + } + + var artifact = new Artifact (parts [1], parts [0], version); + + return artifact; + } + + // TODO: Fix this in MavenNet + public static void SetRepository (this Artifact artifact, MavenRepository repository) + { + var method = artifact.GetType ().GetProperty ("Repository"); + + method.GetSetMethod (true).Invoke (artifact, new [] { repository }); + } + + // TODO: Fix this in MavenNet + public static Project ParsePom (string pomFile) + { + var parser = typeof (Project).Assembly.GetType ("MavenNet.PomParser"); + var method = parser.GetMethod ("Parse"); + + using var sr = File.OpenRead (pomFile); + + var pom = method.Invoke (null, new [] { sr }) as Project; + + return pom!; + } + + public static Artifact? CheckForNeededParentPom (string pomFile) + => ParsePom (pomFile).GetParentPom (); + + public static Artifact? GetParentPom (this Project? pom) + { + if (pom?.Parent != null) + return new Artifact (pom.Parent.ArtifactId, pom.Parent.GroupId, pom.Parent.Version); + + return null; + } + + // Returns artifact output path + public static async Task DownloadPayload (Artifact artifact, string cacheDir, TaskLoggingHelper log) + { + var version = artifact.Versions.First (); + + var output_directory = Path.Combine (cacheDir, artifact.GetRepositoryCacheName (), artifact.GroupId, artifact.Id, version); + + Directory.CreateDirectory (output_directory); + + var filename = $"{artifact.GroupId}_{artifact.Id}"; + var jar_filename = Path.Combine (output_directory, Path.Combine ($"{filename}.jar")); + var aar_filename = Path.Combine (output_directory, Path.Combine ($"{filename}.aar")); + + // We don't need to redownload if we already have a cached copy + if (File.Exists (jar_filename)) + return jar_filename; + + if (File.Exists (aar_filename)) + return aar_filename; + + if (await TryDownloadPayload (artifact, jar_filename) is not string jar_error) + return jar_filename; + + if (await TryDownloadPayload (artifact, aar_filename) is not string aar_error) + return aar_filename; + + log.LogError ("Cannot download artifact '{0}:{1}'.\n- {2}: {3}\n- {4}: {5}", artifact.GroupId, artifact.Id, Path.GetFileName (jar_filename), jar_error, Path.GetFileName (aar_filename), aar_error); + + return null; + } + + // Returns artifact output path + public static async Task DownloadPom (Artifact artifact, string cacheDir, TaskLoggingHelper log, bool isParent = false) + { + var version = artifact.Versions.First (); + var output_directory = Path.Combine (cacheDir, artifact.GetRepositoryCacheName (), artifact.GroupId, artifact.Id, version); + + Directory.CreateDirectory (output_directory); + + var filename = $"{artifact.GroupId}_{artifact.Id}"; + var pom_filename = Path.Combine (output_directory, Path.Combine ($"{filename}.pom")); + + // We don't need to redownload if we already have a cached copy + if (File.Exists (pom_filename)) + return pom_filename; + + if (await TryDownloadPayload (artifact, pom_filename) is not string pom_error) + return pom_filename; + + log.LogError ("Cannot download {4}POM file for artifact '{0}:{1}'.\n- {2}: {3}", artifact.GroupId, artifact.Id, Path.GetFileName (pom_filename), pom_error, isParent ? "parent " : ""); + + return null; + } + + // Return value indicates download success + static async Task TryDownloadPayload (Artifact artifact, string filename) + { + try { + using var src = await artifact.OpenLibraryFile (artifact.Versions.First (), Path.GetExtension (filename)); + using var sw = File.Create (filename); + + await src.CopyToAsync (sw); + + return null; + } catch (Exception ex) { + return ex.Message; + } + } + + public static string GetRepositoryCacheName (this Artifact artifact) + { + var type = artifact.Repository; + + if (type is MavenCentralRepository) + return "central"; + + if (type is GoogleMavenRepository) + return "google"; + + if (type is UrlMavenRepository url) { + using var hasher = SHA256.Create (); + var hash = hasher.ComputeHash (Encoding.UTF8.GetBytes (url.BaseUri.ToString ())); + return Convert.ToBase64String (hash); + } + + // Should never be hit + throw new ArgumentException ($"Unexpected repository type: {type.GetType ()}"); + } + + public static void FixDependency (Project project, Project? parent, Dependency dependency) + { + // Handle Parent POM + if ((string.IsNullOrEmpty (dependency.Version) || string.IsNullOrEmpty (dependency.Scope)) && parent != null) { + var parent_dependency = parent.FindParentDependency (dependency); + + // Try to fish a version out of the parent POM + if (string.IsNullOrEmpty (dependency.Version)) + dependency.Version = ReplaceVersionProperties (parent, parent_dependency?.Version); + + // Try to fish a scope out of the parent POM + if (string.IsNullOrEmpty (dependency.Scope)) + dependency.Scope = parent_dependency?.Scope; + } + + var version = dependency.Version; + + if (string.IsNullOrWhiteSpace (version)) + return; + + version = ReplaceVersionProperties (project, version); + + // VersionRange.Parse cannot handle single number versions that we sometimes see in Maven, like "1". + // Fix them to be "1.0". + // https://github.com/NuGet/Home/issues/10342 + if (version != null && !version.Contains (".")) + version += ".0"; + + dependency.Version = version; + } + + static string? ReplaceVersionProperties (Project project, string? version) + { + // Handle versions with Properties, like: + // + // 1.8 + // 2.8.6 + // + // + // + // com.google.code.gson + // gson + // ${gson.version} + // + // + if (string.IsNullOrWhiteSpace (version) || project?.Properties == null) + return version; + + foreach (var prop in project.Properties.Any) + version = version?.Replace ($"${{{prop.Name.LocalName}}}", prop.Value); + + return version; + } + + public static bool IsCompileDependency (this Dependency dependency) => string.IsNullOrWhiteSpace (dependency.Scope) || dependency.Scope.ToLowerInvariant ().Equals ("compile"); + + public static bool IsRuntimeDependency (this Dependency dependency) => dependency?.Scope != null && dependency.Scope.ToLowerInvariant ().Equals ("runtime"); + + public static Dependency? FindParentDependency (this Project project, Dependency dependency) + { + return project.DependencyManagement?.Dependencies?.FirstOrDefault ( + d => d.GroupAndArtifactId () == dependency.GroupAndArtifactId () && d.Classifier != "sources"); + } + + public static string GroupAndArtifactId (this Dependency dependency) => $"{dependency.GroupId}.{dependency.ArtifactId}"; +} 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 c94ad582680..01e7ea5fe14 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -34,6 +34,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets index fbd0fd842c7..404f465ae24 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets @@ -55,6 +55,11 @@ PreserveNewest Xamarin.Android.Bindings.JarToXml.targets + + PreserveNewest + Xamarin.Android.Bindings.Maven.targets + PreserveNewest From 0da40eb275361cf3412c3ae38907f4ffadeb689e Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 17 Oct 2023 10:59:52 -1000 Subject: [PATCH 02/12] Apply suggestions from code review Co-authored-by: Jonathan Peppers --- .../Xamarin/Android/Xamarin.Android.Bindings.Maven.targets | 3 ++- src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets index 3469be99d53..ad2f2b0d92c 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets @@ -18,7 +18,8 @@ This file contains MSBuild targets used to enable @(AndroidMavenLibrary) support $(MavenCacheDirectory)\ - + diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs index 93921ff79cb..ab87cc518a9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs @@ -215,9 +215,9 @@ public static void FixDependency (Project project, Project? parent, Dependency d return version; } - public static bool IsCompileDependency (this Dependency dependency) => string.IsNullOrWhiteSpace (dependency.Scope) || dependency.Scope.ToLowerInvariant ().Equals ("compile"); + public static bool IsCompileDependency (this Dependency dependency) => string.IsNullOrWhiteSpace (dependency.Scope) || string.IndexOf("compile", StringComparison.OrdinalIgnoreCase) != -1; - public static bool IsRuntimeDependency (this Dependency dependency) => dependency?.Scope != null && dependency.Scope.ToLowerInvariant ().Equals ("runtime"); + public static bool IsRuntimeDependency (this Dependency dependency) => dependency?.Scope != null && string.IndexOf("runtime", StringComparison.OrdinalIgnoreCase) != -1; public static Dependency? FindParentDependency (this Project project, Dependency dependency) { From 167575da263a44fd9d94d2cbf4583092870f7a70 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 17 Oct 2023 13:10:35 -1000 Subject: [PATCH 03/12] Use CancellationToken where available. --- .../Tasks/MavenDownloadTask.cs | 57 ++----------------- .../Utilities/MavenExtensions.cs | 19 ++++--- 2 files changed, 14 insertions(+), 62 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs index 300085f95b3..77a3457644b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs @@ -27,7 +27,7 @@ public class MavenDownloadTask : AndroidAsyncTask public ITaskItem []? AndroidMavenLibraries { get; set; } /// - /// The set of requested Maven libraries that we were able to successfully download. + /// The set of requested Maven libraries that we were able to successfully acquire. /// [Output] public ITaskItem []? ResolvedAndroidMavenLibraries { get; set; } @@ -51,13 +51,6 @@ public async override System.Threading.Tasks.Task RunTaskAsync () if (artifact is null) continue; - // Check for local files - if (GetLocalArtifactOrDefault (library, Log) is TaskItem cached_result) { - library.CopyMetadataTo (cached_result); - resolved.Add (cached_result); - continue; - } - // Check for repository files if (await GetRepositoryArtifactOrDefault (artifact, library, Log) is TaskItem result) { library.CopyMetadataTo (result); @@ -69,48 +62,6 @@ public async override System.Threading.Tasks.Task RunTaskAsync () ResolvedAndroidMavenLibraries = resolved.ToArray (); } - TaskItem? GetLocalArtifactOrDefault (ITaskItem item, TaskLoggingHelper log) - { - // Handles a Repository="file" entry, like: - // - var type = item.GetMetadataOrDefault ("Repository", "Central"); - - if (type.Equals ("file", StringComparison.InvariantCultureIgnoreCase)) { - var artifact_file = item.GetMetadataOrDefault ("PackageFile", string.Empty); - var pom_file = item.GetMetadataOrDefault ("PomFile", string.Empty); - - if (!artifact_file.HasValue () || !pom_file.HasValue ()) { - log.LogError ("'PackageFile' and 'PomFile' must be specified when using a 'File' repository."); - return null; - } - - if (!File.Exists (artifact_file)) { - log.LogError ("Specified package file '{0}' does not exist.", artifact_file); - return null; - } - - if (!File.Exists (pom_file)) { - log.LogError ("Specified pom file '{0}' does not exist.", pom_file); - return null; - } - - var result = new TaskItem (artifact_file); - - result.SetMetadata ("ArtifactSpec", item.ItemSpec); - result.SetMetadata ("ArtifactFile", artifact_file); - result.SetMetadata ("ArtifactPom", pom_file); - - return result; - } - - return null; - } - async System.Threading.Tasks.Task GetRepositoryArtifactOrDefault (Artifact artifact, ITaskItem item, TaskLoggingHelper log) { // Handles a Repository="Central|Google|" entry, like: @@ -129,13 +80,13 @@ public async override System.Threading.Tasks.Task RunTaskAsync () artifact.SetRepository (repository); // Download artifact - var artifact_file = await MavenExtensions.DownloadPayload (artifact, MavenCacheDirectory, Log); + var artifact_file = await MavenExtensions.DownloadPayload (artifact, MavenCacheDirectory, Log, CancellationToken); if (artifact_file is null) return null; // Download POM - var pom_file = await MavenExtensions.DownloadPom (artifact, MavenCacheDirectory, Log); + var pom_file = await MavenExtensions.DownloadPom (artifact, MavenCacheDirectory, Log, CancellationToken); if (pom_file is null) return null; @@ -170,7 +121,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () artifact.SetRepository (repository); // Download POM - var pom_file = await MavenExtensions.DownloadPom (artifact, MavenCacheDirectory, Log); + var pom_file = await MavenExtensions.DownloadPom (artifact, MavenCacheDirectory, Log, CancellationToken); if (pom_file is null) return null; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs index ab87cc518a9..974867347a4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Threading; using System.Threading.Tasks; using MavenNet; using MavenNet.Models; @@ -73,7 +74,7 @@ public static Project ParsePom (string pomFile) } // Returns artifact output path - public static async Task DownloadPayload (Artifact artifact, string cacheDir, TaskLoggingHelper log) + public static async Task DownloadPayload (Artifact artifact, string cacheDir, TaskLoggingHelper log, CancellationToken cancellationToken) { var version = artifact.Versions.First (); @@ -92,10 +93,10 @@ public static Project ParsePom (string pomFile) if (File.Exists (aar_filename)) return aar_filename; - if (await TryDownloadPayload (artifact, jar_filename) is not string jar_error) + if (await TryDownloadPayload (artifact, jar_filename, cancellationToken) is not string jar_error) return jar_filename; - if (await TryDownloadPayload (artifact, aar_filename) is not string aar_error) + if (await TryDownloadPayload (artifact, aar_filename, cancellationToken) is not string aar_error) return aar_filename; log.LogError ("Cannot download artifact '{0}:{1}'.\n- {2}: {3}\n- {4}: {5}", artifact.GroupId, artifact.Id, Path.GetFileName (jar_filename), jar_error, Path.GetFileName (aar_filename), aar_error); @@ -104,7 +105,7 @@ public static Project ParsePom (string pomFile) } // Returns artifact output path - public static async Task DownloadPom (Artifact artifact, string cacheDir, TaskLoggingHelper log, bool isParent = false) + public static async Task DownloadPom (Artifact artifact, string cacheDir, TaskLoggingHelper log, CancellationToken cancellationToken, bool isParent = false) { var version = artifact.Versions.First (); var output_directory = Path.Combine (cacheDir, artifact.GetRepositoryCacheName (), artifact.GroupId, artifact.Id, version); @@ -118,7 +119,7 @@ public static Project ParsePom (string pomFile) if (File.Exists (pom_filename)) return pom_filename; - if (await TryDownloadPayload (artifact, pom_filename) is not string pom_error) + if (await TryDownloadPayload (artifact, pom_filename, cancellationToken) is not string pom_error) return pom_filename; log.LogError ("Cannot download {4}POM file for artifact '{0}:{1}'.\n- {2}: {3}", artifact.GroupId, artifact.Id, Path.GetFileName (pom_filename), pom_error, isParent ? "parent " : ""); @@ -127,13 +128,13 @@ public static Project ParsePom (string pomFile) } // Return value indicates download success - static async Task TryDownloadPayload (Artifact artifact, string filename) + static async Task TryDownloadPayload (Artifact artifact, string filename, CancellationToken cancellationToken) { try { using var src = await artifact.OpenLibraryFile (artifact.Versions.First (), Path.GetExtension (filename)); using var sw = File.Create (filename); - await src.CopyToAsync (sw); + await src.CopyToAsync (sw, 81920, cancellationToken); return null; } catch (Exception ex) { @@ -215,9 +216,9 @@ public static void FixDependency (Project project, Project? parent, Dependency d return version; } - public static bool IsCompileDependency (this Dependency dependency) => string.IsNullOrWhiteSpace (dependency.Scope) || string.IndexOf("compile", StringComparison.OrdinalIgnoreCase) != -1; + public static bool IsCompileDependency (this Dependency dependency) => string.IsNullOrWhiteSpace (dependency.Scope) || dependency.Scope.IndexOf ("compile", StringComparison.OrdinalIgnoreCase) != -1; - public static bool IsRuntimeDependency (this Dependency dependency) => dependency?.Scope != null && string.IndexOf("runtime", StringComparison.OrdinalIgnoreCase) != -1; + public static bool IsRuntimeDependency (this Dependency dependency) => dependency?.Scope != null && dependency.Scope.IndexOf ("runtime", StringComparison.OrdinalIgnoreCase) != -1; public static Dependency? FindParentDependency (this Project project, Dependency dependency) { From 62d33b9db9b1b6d89238a1756a2a0295cd4ad439 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Thu, 19 Oct 2023 09:36:09 -1000 Subject: [PATCH 04/12] Add tests. --- .../Tasks/MavenDownloadTask.cs | 2 - .../BindingBuildTest.cs | 19 ++ .../Tasks/MavenDownloadTaskTests.cs | 199 ++++++++++++++++++ .../Utilities/MavenExtensions.cs | 15 +- 4 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs index 77a3457644b..0fe5ffb1832 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using MavenNet; using MavenNet.Models; using Microsoft.Android.Build.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -using MonoDroid.Utils; namespace Xamarin.Android.Tasks; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs index 7fb2f61bfd7..3bb16b0eda0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs @@ -815,5 +815,24 @@ public void AarWithRClassesJar () Assert.IsTrue (appBuilder.Build (app), "App build should have succeeded."); } } + + [Test] + public void AndroidMavenLibrary () + { + var item = new BuildItem ("AndroidMavenLibrary", "com.google.auto.value:auto-value-annotations"); + item.Metadata.Add ("Version", "1.10.4"); + + var proj = new XamarinAndroidBindingProject { + Jars = { item } + }; + + using (var b = CreateDllBuilder ()) { + Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + + // Ensure the generated file exists + var cs_file = b.Output.GetIntermediaryPath (Path.Combine ("generated", "src", "Com.Google.Auto.Value.AutoValueAttribute.cs")); + FileAssert.Exists (cs_file); + } + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs new file mode 100644 index 00000000000..fa1b4005867 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; +using Xamarin.Android.Tasks; +using Task = System.Threading.Tasks.Task; +namespace Xamarin.Android.Build.Tests; + +public class MavenDownloadTaskTests +{ + [Test] + public async Task MissingVersionMetadata () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownloadTask { + BuildEngine = engine, + AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material:material", null)], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("Item is missing required metadata 'Version'", engine.Errors [0].Message); + } + + [Test] + public async Task InvalidArtifactSpecification_WrongNumberOfParts () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownloadTask { + BuildEngine = engine, + AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material", "1.0.0")], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("Artifact specification 'com.google.android.material' is invalid.", engine.Errors [0].Message); + } + + [Test] + public async Task InvalidArtifactSpecification_EmptyPart () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownloadTask { + BuildEngine = engine, + AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material: ", "1.0.0")], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("Artifact specification 'com.google.android.material: ' is invalid.", engine.Errors [0].Message); + } + + [Test] + public async Task UnknownRepository () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownloadTask { + BuildEngine = engine, + AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material:material", "1.0.0", "bad-repo")], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("Unknown Maven repository: 'bad-repo'.", engine.Errors [0].Message); + } + + [Test] + public async Task UnknownArtifact () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownloadTask { + BuildEngine = engine, + MavenCacheDirectory = Path.GetTempPath (), + AndroidMavenLibraries = [CreateMavenTaskItem ("com.example:dummy", "1.0.0")], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("Cannot download artifact 'com.example:dummy'.\n- com.example_dummy.jar: Response status code does not indicate success: 404 (Not Found).\n- com.example_dummy.aar: Response status code does not indicate success: 404 (Not Found).", engine.Errors [0].Message); + } + + [Test] + public async Task UnknownPom () + { + var temp_cache_dir = Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString ()); + + try { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownloadTask { + BuildEngine = engine, + MavenCacheDirectory = temp_cache_dir, + AndroidMavenLibraries = [CreateMavenTaskItem ("com.example:dummy", "1.0.0")], + }; + + // Create the dummy jar so we bypass that step and try to download the dummy pom + var dummy_jar = Path.Combine (temp_cache_dir, "central", "com.example", "dummy", "1.0.0", "com.example_dummy.jar"); + Directory.CreateDirectory (Path.GetDirectoryName (dummy_jar)); + + using (File.Create (dummy_jar)) { } + + await task.RunTaskAsync (); + + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("Cannot download POM file for artifact 'com.example:dummy'.\n- com.example_dummy.pom: Response status code does not indicate success: 404 (Not Found).", engine.Errors [0].Message); + } finally { + DeleteTempDirectory (temp_cache_dir); + } + } + + [Test] + public async Task MavenCentralSuccess () + { + var temp_cache_dir = Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString ()); + + try { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownloadTask { + BuildEngine = engine, + MavenCacheDirectory = temp_cache_dir, + AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.auto.value:auto-value-annotations", "1.10.4")], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (0, engine.Errors.Count); + Assert.AreEqual (1, task.ResolvedAndroidMavenLibraries.Length); + + var output_item = task.ResolvedAndroidMavenLibraries [0]; + + Assert.AreEqual ("com.google.auto.value:auto-value-annotations", output_item.GetMetadata ("ArtifactSpec")); + Assert.AreEqual (Path.Combine (temp_cache_dir, "central", "com.google.auto.value", "auto-value-annotations", "1.10.4", "com.google.auto.value_auto-value-annotations.jar"), output_item.GetMetadata ("ArtifactFile")); + Assert.AreEqual (Path.Combine (temp_cache_dir, "central", "com.google.auto.value", "auto-value-annotations", "1.10.4", "com.google.auto.value_auto-value-annotations.pom"), output_item.GetMetadata ("ArtifactPom")); + + Assert.True (File.Exists (output_item.GetMetadata ("ArtifactFile"))); + Assert.True (File.Exists (output_item.GetMetadata ("ArtifactPom"))); + } finally { + DeleteTempDirectory (temp_cache_dir); + } + } + + [Test] + public async Task MavenGoogleSuccess () + { + var temp_cache_dir = Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString ()); + + try { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownloadTask { + BuildEngine = engine, + MavenCacheDirectory = temp_cache_dir, + AndroidMavenLibraries = [CreateMavenTaskItem ("androidx.core:core", "1.12.0", "Google")], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (0, engine.Errors.Count); + Assert.AreEqual (1, task.ResolvedAndroidMavenLibraries.Length); + + var output_item = task.ResolvedAndroidMavenLibraries [0]; + + Assert.AreEqual ("androidx.core:core", output_item.GetMetadata ("ArtifactSpec")); + Assert.AreEqual (Path.Combine (temp_cache_dir, "google", "androidx.core", "core", "1.12.0", "androidx.core_core.aar"), output_item.GetMetadata ("ArtifactFile")); + Assert.AreEqual (Path.Combine (temp_cache_dir, "google", "androidx.core", "core", "1.12.0", "androidx.core_core.pom"), output_item.GetMetadata ("ArtifactPom")); + + Assert.True (File.Exists (output_item.GetMetadata ("ArtifactFile"))); + Assert.True (File.Exists (output_item.GetMetadata ("ArtifactPom"))); + } finally { + DeleteTempDirectory (temp_cache_dir); + } + } + + ITaskItem CreateMavenTaskItem (string name, string version, string repository = null) + { + var item = new TaskItem (name); + + if (version is not null) + item.SetMetadata ("Version", version); + if (repository is not null) + item.SetMetadata ("Repository", repository); + + return item; + } + + void DeleteTempDirectory (string dir) + { + try { + Directory.Delete (dir, true); + } catch { + // Ignore any cleanup failure + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs index 974867347a4..e10e19fcc8c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs @@ -6,6 +6,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; using MavenNet; using MavenNet.Models; using Microsoft.Build.Utilities; @@ -49,17 +51,18 @@ public static void SetRepository (this Artifact artifact, MavenRepository reposi method.GetSetMethod (true).Invoke (artifact, new [] { repository }); } - // TODO: Fix this in MavenNet public static Project ParsePom (string pomFile) { - var parser = typeof (Project).Assembly.GetType ("MavenNet.PomParser"); - var method = parser.GetMethod ("Parse"); + Project result = null; - using var sr = File.OpenRead (pomFile); + var serializer = new XmlSerializer (typeof (Project)); - var pom = method.Invoke (null, new [] { sr }) as Project; + using (var sr = File.OpenRead (pomFile)) + result = (Project) serializer.Deserialize (new XmlTextReader (sr) { + Namespaces = false, + }); - return pom!; + return result; } public static Artifact? CheckForNeededParentPom (string pomFile) From 8a228ad7b3ffd0ce75ef315b9926d3256fe7ca62 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Mon, 23 Oct 2023 10:39:16 -1000 Subject: [PATCH 05/12] Localization, third party notices --- .../xaprepare/ThirdPartyNotices/MavenNet.cs | 40 +++++++++++++ .../Properties/Resources.Designer.cs | 60 ++++++++++++++++++- .../Properties/Resources.resx | 49 ++++++++++++++- .../Tasks/MavenDownloadTask.cs | 6 +- .../BindingBuildTest.cs | 1 + .../Tasks/MavenDownloadTaskTests.cs | 10 ++-- .../Utilities/ITaskItemExtensions.cs | 5 +- .../Utilities/MavenExtensions.cs | 10 +++- 8 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 build-tools/xaprepare/xaprepare/ThirdPartyNotices/MavenNet.cs diff --git a/build-tools/xaprepare/xaprepare/ThirdPartyNotices/MavenNet.cs b/build-tools/xaprepare/xaprepare/ThirdPartyNotices/MavenNet.cs new file mode 100644 index 00000000000..a09a63de12f --- /dev/null +++ b/build-tools/xaprepare/xaprepare/ThirdPartyNotices/MavenNet.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Xamarin.Android.Prepare +{ + [TPN] + class MavenNet_TPN : ThirdPartyNotice + { + static readonly Uri url = new Uri ("https://github.com/Redth/MavenNet/"); + + public override string LicenseFile => string.Empty; + public override string Name => "Redth/MavenNet"; + public override Uri SourceUrl => url; + public override string LicenseText => @" +MIT License + +Copyright (c) 2017 Jonathan Dick + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the ""Software""), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE."; + + public override bool Include (bool includeExternalDeps, bool includeBuildDeps) => includeExternalDeps; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs index 351165d04d7..525498f9096 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs @@ -1123,7 +1123,65 @@ public static string XA4233 { return ResourceManager.GetString("XA4233", resourceCulture); } } - + + /// + /// Looks up a localized string similar to '<{0}>' item '{1}' is missing required metadata '{2}'. + /// + public static string XA4234 { + get { + return ResourceManager.GetString("XA4234", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maven artifact specification '{0}' is invalid. The correct format is 'group_id:artifact_id'.. + /// + public static string XA4235 { + get { + return ResourceManager.GetString("XA4235", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot download Maven artifact '{0}:{1}'. + ///- {2}: {3} + ///- {4}: {5}. + /// + public static string XA4236 { + get { + return ResourceManager.GetString("XA4236", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot download POM file for Maven artifact '{0}:{1}'. + ///- {2}: {3}. + /// + public static string XA4237 { + get { + return ResourceManager.GetString("XA4237", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot download parent POM file for Maven artifact '{0}:{1}'. + ///- {2}: {3}. + /// + public static string XA4238 { + get { + return ResourceManager.GetString("XA4238", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown Maven repository: '{0}'.. + /// + public static string XA4239 { + get { + return ResourceManager.GetString("XA4239", resourceCulture); + } + } + /// /// Looks up a localized string similar to Native library '{0}' will not be bundled because it has an unsupported ABI. Move this file to a directory with a valid Android ABI name such as 'libs/armeabi-v7a/'.. /// diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index f6647ba8e92..5eeed4389f3 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -949,4 +949,51 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS {0} - An Android Resource Identifier. - + + '<{0}>' item '{1}' is missing required metadata '{2}' + {0} - The MSBuild ItemGroup Item name +{1} - The MSBuild Item ItemSpec +{2} - The omitted MSBuild Item metadata attribute + + + Maven artifact specification '{0}' is invalid. The correct format is 'group_id:artifact_id'. + The following are literal names and should not be translated: Maven, group_id, artifact_id +{0} - A Maven artifact specification + + + Cannot download Maven artifact '{0}:{1}'. +- {2}: {3} +- {4}: {5} + The following are literal names and should not be translated: Maven +{0} - Maven artifact group id +{1} - Maven artifact id +{2} - The .jar filename we tried to download +{3} - The HttpClient reported download exception message +{4} - The .aar filename we tried to download +{5} - The HttpClient provided download exception message + + + Cannot download POM file for Maven artifact '{0}:{1}'. +- {2}: {3} + The following are literal names and should not be translated: POM, Maven +{0} - Maven artifact group id +{1} - Maven artifact id +{2} - The .pom filename we tried to download +{3} - The HttpClient reported download exception message + + + + Cannot download parent POM file for Maven artifact '{0}:{1}'. +- {2}: {3} + The following are literal names and should not be translated: POM, Maven +{0} - Maven artifact group id +{1} - Maven artifact id +{2} - The .pom filename we tried to download +{3} - The HttpClient reported download exception message + + + Unknown Maven repository: '{0}'. + The following are literal names and should not be translated: Maven +{0} - User supplied Maven repository type + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs index 0fe5ffb1832..858b01e7988 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs @@ -39,7 +39,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () // Validate artifact var id = library.ItemSpec; - var version = library.GetRequiredMetadata ("Version", Log); + var version = library.GetRequiredMetadata ("AndroidMavenLibrary", "Version", Log); if (version is null) continue; @@ -100,7 +100,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () async System.Threading.Tasks.Task TryGetParentPom (ITaskItem item, TaskLoggingHelper log) { - var child_pom_file = item.GetRequiredMetadata ("ArtifactPom", Log); + var child_pom_file = item.GetRequiredMetadata ("AndroidMavenLibrary", "ArtifactPom", Log); // Shouldn't be possible because we just created this items if (child_pom_file is null) @@ -149,7 +149,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () repo = MavenRepository.FromUrl (type); if (repo is null) - Log.LogError ("Unknown Maven repository: '{0}'.", type); + Log.LogCodedError ("XA4239", Properties.Resources.XA4239, type); return repo; } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs index 3bb16b0eda0..7c1eb96a647 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs @@ -819,6 +819,7 @@ public void AarWithRClassesJar () [Test] public void AndroidMavenLibrary () { + // Test that downloads .jar from Maven and successfully binds it var item = new BuildItem ("AndroidMavenLibrary", "com.google.auto.value:auto-value-annotations"); item.Metadata.Add ("Version", "1.10.4"); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs index fa1b4005867..f0fb985b705 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs @@ -22,7 +22,7 @@ public async Task MissingVersionMetadata () await task.RunTaskAsync (); Assert.AreEqual (1, engine.Errors.Count); - Assert.AreEqual ("Item is missing required metadata 'Version'", engine.Errors [0].Message); + Assert.AreEqual ("'' item 'com.google.android.material:material' is missing required metadata 'Version'", engine.Errors [0].Message); } [Test] @@ -37,7 +37,7 @@ public async Task InvalidArtifactSpecification_WrongNumberOfParts () await task.RunTaskAsync (); Assert.AreEqual (1, engine.Errors.Count); - Assert.AreEqual ("Artifact specification 'com.google.android.material' is invalid.", engine.Errors [0].Message); + Assert.AreEqual ("Maven artifact specification 'com.google.android.material' is invalid. The correct format is 'group_id:artifact_id'.", engine.Errors [0].Message); } [Test] @@ -52,7 +52,7 @@ public async Task InvalidArtifactSpecification_EmptyPart () await task.RunTaskAsync (); Assert.AreEqual (1, engine.Errors.Count); - Assert.AreEqual ("Artifact specification 'com.google.android.material: ' is invalid.", engine.Errors [0].Message); + Assert.AreEqual ("Maven artifact specification 'com.google.android.material: ' is invalid. The correct format is 'group_id:artifact_id'.", engine.Errors [0].Message); } [Test] @@ -83,7 +83,7 @@ public async Task UnknownArtifact () await task.RunTaskAsync (); Assert.AreEqual (1, engine.Errors.Count); - Assert.AreEqual ("Cannot download artifact 'com.example:dummy'.\n- com.example_dummy.jar: Response status code does not indicate success: 404 (Not Found).\n- com.example_dummy.aar: Response status code does not indicate success: 404 (Not Found).", engine.Errors [0].Message); + Assert.AreEqual ($"Cannot download Maven artifact 'com.example:dummy'.{Environment.NewLine}- com.example_dummy.jar: Response status code does not indicate success: 404 (Not Found).{Environment.NewLine}- com.example_dummy.aar: Response status code does not indicate success: 404 (Not Found).", engine.Errors [0].Message.ReplaceLineEndings ()); } [Test] @@ -108,7 +108,7 @@ public async Task UnknownPom () await task.RunTaskAsync (); Assert.AreEqual (1, engine.Errors.Count); - Assert.AreEqual ("Cannot download POM file for artifact 'com.example:dummy'.\n- com.example_dummy.pom: Response status code does not indicate success: 404 (Not Found).", engine.Errors [0].Message); + Assert.AreEqual ($"Cannot download POM file for Maven artifact 'com.example:dummy'.{Environment.NewLine}- com.example_dummy.pom: Response status code does not indicate success: 404 (Not Found).", engine.Errors [0].Message.ReplaceLineEndings ()); } finally { DeleteTempDirectory (temp_cache_dir); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs index a3f80305ac8..f91af2d232f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Microsoft.Android.Build.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -32,12 +33,12 @@ public static string GetMetadataOrDefault (this ITaskItem item, string name, str return value; } - public static string? GetRequiredMetadata (this ITaskItem item, string name, TaskLoggingHelper log) + public static string? GetRequiredMetadata (this ITaskItem item, string itemName, string name, TaskLoggingHelper log) { var value = item.GetMetadata (name); if (string.IsNullOrWhiteSpace (value)) { - log.LogError ("Item is missing required metadata '{0}'", name); + log.LogCodedError ("XA4234", Properties.Resources.XA4234, itemName, item.ToString (), name); return null; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs index e10e19fcc8c..0b06ed71ae7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs @@ -10,6 +10,7 @@ using System.Xml.Serialization; using MavenNet; using MavenNet.Models; +using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; namespace Xamarin.Android.Tasks; @@ -34,7 +35,7 @@ public static T [] OrEmpty (this T []? value) var parts = id.Split (separator, StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2 || parts.Any (string.IsNullOrWhiteSpace)) { - log.LogError ("Artifact specification '{0}' is invalid.", id); + log.LogCodedError ("XA4235", Properties.Resources.XA4235, id); return null; } @@ -102,7 +103,7 @@ public static Project ParsePom (string pomFile) if (await TryDownloadPayload (artifact, aar_filename, cancellationToken) is not string aar_error) return aar_filename; - log.LogError ("Cannot download artifact '{0}:{1}'.\n- {2}: {3}\n- {4}: {5}", artifact.GroupId, artifact.Id, Path.GetFileName (jar_filename), jar_error, Path.GetFileName (aar_filename), aar_error); + log.LogCodedError ("XA4236", Properties.Resources.XA4236, artifact.GroupId, artifact.Id, Path.GetFileName (jar_filename), jar_error, Path.GetFileName (aar_filename), aar_error); return null; } @@ -125,7 +126,10 @@ public static Project ParsePom (string pomFile) if (await TryDownloadPayload (artifact, pom_filename, cancellationToken) is not string pom_error) return pom_filename; - log.LogError ("Cannot download {4}POM file for artifact '{0}:{1}'.\n- {2}: {3}", artifact.GroupId, artifact.Id, Path.GetFileName (pom_filename), pom_error, isParent ? "parent " : ""); + if (!isParent) + log.LogCodedError ("XA4237", Properties.Resources.XA4237, artifact.GroupId, artifact.Id, Path.GetFileName (pom_filename), pom_error); + else + log.LogCodedError ("XA4238", Properties.Resources.XA4238, artifact.GroupId, artifact.Id, Path.GetFileName (pom_filename), pom_error); return null; } From 1cb97430c060d92064475d5920c3d0e7e80c26b1 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 24 Oct 2023 10:50:10 -1000 Subject: [PATCH 06/12] Add docs. --- Documentation/guides/AndroidMavenLibrary.md | 59 +++++++++++++++++++ .../guides/building-apps/build-items.md | 18 ++++++ 2 files changed, 77 insertions(+) create mode 100644 Documentation/guides/AndroidMavenLibrary.md diff --git a/Documentation/guides/AndroidMavenLibrary.md b/Documentation/guides/AndroidMavenLibrary.md new file mode 100644 index 00000000000..b1f59c642e4 --- /dev/null +++ b/Documentation/guides/AndroidMavenLibrary.md @@ -0,0 +1,59 @@ +# AndroidMavenLibrary + +Note: This feature is only available in .NET 9+. + +## Description + +`` allows a Maven artifact to be specified which will automatically be downloaded and added to a .NET Android binding project. This can be useful to simplify maintenance of .NET Android bindings for artifacts hosted in Maven. + +## Specification + + A basic use of `` looks like: + +```xml + + + + +``` + +This will do two things at build time: +- Download the Java [artifact](https://central.sonatype.com/artifact/com.squareup.okhttp3/okhttp/4.9.3) with group id `com.squareup.okhttp3`, artifact id `okhttp`, and version `4.9.3` from [Maven Central](https://central.sonatype.com/) to a local cache (if not already cached). +- Add the cached package to the .NET Android bindings build as an [``](https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/building-apps/build-items.md#androidlibrary). + +### Options + +`` defaults to using Maven Central, however it should support any Maven repository that does not require authentication. This can be controlled with the `Repository` attribute. + +Supported values are `Central` (default), `Google`, or a URL to another Maven repository. + +```xml + + + +``` + +```xml + + + +``` + +Additionally, any attributes applied to the `` element will be copied to the `` it creates internally. Thus, [attributes](https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/OneDotNetEmbeddedResources.md#msbuild-item-groups) like `Bind` and `Pack` can be used to control the binding process. (Both default to `true`.) + +```xml + + + +``` diff --git a/Documentation/guides/building-apps/build-items.md b/Documentation/guides/building-apps/build-items.md index 6da648100fe..3e0ff017700 100644 --- a/Documentation/guides/building-apps/build-items.md +++ b/Documentation/guides/building-apps/build-items.md @@ -174,6 +174,24 @@ installing app bundles. This build action was introduced in Xamarin.Android 11.3. +## AndroidMavenLibrary + +`` allows a Maven artifact to be specified which will +automatically be downloaded and added to a .NET Android binding project. +This can be useful to simplify maintenance of .NET Android bindings for artifacts +hosted in Maven. + +```xml + + + + +``` +See the [AndroidMavenLibrary documentation](../AndroidMavenLibrary.md) +for more details. + +This build action was introduced in .NET 9. + ## AndroidNativeLibrary [Native libraries](~/android/platform/native-libraries.md) From 7a8c6f8bc289b522619097588b8a217d0fd07494 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Thu, 26 Oct 2023 11:13:49 -1000 Subject: [PATCH 07/12] Update MavenNet. --- build-tools/installers/create-installers.targets | 1 + .../Tasks/MavenDownloadTask.cs | 4 ++-- .../Utilities/MavenExtensions.cs | 8 -------- .../Xamarin.Android.Build.Tasks.csproj | 2 +- .../Xamarin.Android.Build.Tasks.targets | 2 ++ 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index 68ed22efabf..d8d433ff16f 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -111,6 +111,7 @@ <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)%(Identity)\libZipSharp.resources.dll')" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libZipSharp.pdb" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)MavenNet.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)MavenNet.pdb" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.Unix.dll" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.Unix.pdb" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Microsoft.Android.Build.BaseTasks.dll" /> diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs index 858b01e7988..780c076567f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownloadTask.cs @@ -75,7 +75,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () if (repository is null) return null; - artifact.SetRepository (repository); + artifact.Repository = repository; // Download artifact var artifact_file = await MavenExtensions.DownloadPayload (artifact, MavenCacheDirectory, Log, CancellationToken); @@ -116,7 +116,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () if (repository is null) return null; - artifact.SetRepository (repository); + artifact.Repository = repository; // Download POM var pom_file = await MavenExtensions.DownloadPom (artifact, MavenCacheDirectory, Log, CancellationToken); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs index 0b06ed71ae7..609d7b6e468 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs @@ -44,14 +44,6 @@ public static T [] OrEmpty (this T []? value) return artifact; } - // TODO: Fix this in MavenNet - public static void SetRepository (this Artifact artifact, MavenRepository repository) - { - var method = artifact.GetType ().GetProperty ("Repository"); - - method.GetSetMethod (true).Invoke (artifact, new [] { repository }); - } - public static Project ParsePom (string pomFile) { Project result = null; 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 01e7ea5fe14..1784be6c0b6 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -34,7 +34,7 @@ - + diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets index 404f465ae24..177f45a22a0 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets @@ -309,6 +309,8 @@ <_ExtraPackageTarget Include="$(OutputPath)\Xamarin.Build.AsyncTask.pdb" /> <_ExtraPackageSource Include="$(PkgMono_Unix)\lib\$(TargetFrameworkNETStandard)\Mono.Unix.pdb" /> <_ExtraPackageTarget Include="$(OutputPath)\Mono.Unix.pdb" /> + <_ExtraPackageSource Include="$(PkgMavenNet)\lib\netstandard2.0\MavenNet.pdb" /> + <_ExtraPackageTarget Include="$(OutputPath)\MavenNet.pdb" /> <_ExtraPackageSource Include="$(MSBuildThisFileDirectory)\Resources\Mono.Unix.dll.config" /> <_ExtraPackageTarget Include="$(OutputPath)\Mono.Unix.dll.config" /> From 37f715933faa586f52d2761a8f263aa6d3677003 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Thu, 26 Oct 2023 13:20:27 -1000 Subject: [PATCH 08/12] Appease PoliCheck. --- build-tools/automation/guardian/source.gdnsuppress | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build-tools/automation/guardian/source.gdnsuppress b/build-tools/automation/guardian/source.gdnsuppress index 7d42cdcacc6..d0a4455d7c6 100644 --- a/build-tools/automation/guardian/source.gdnsuppress +++ b/build-tools/automation/guardian/source.gdnsuppress @@ -231,6 +231,20 @@ "createdDate": "2023-02-22 23:55:29Z", "expirationDate": null, "type": null + }, + "243e199c7aec22377e0363bdca82384278cc36b0674f35697935fde6c45cfd0e": { + "signature": "243e199c7aec22377e0363bdca82384278cc36b0674f35697935fde6c45cfd0e", + "alternativeSignatures": [], + "target": "build-tools/xaprepare/xaprepare/ThirdPartyNotices/MavenNet.cs", + "memberOf": [ + "default" + ], + "tool": "policheck", + "ruleId": "79607", + "justification": "Reference to a proper name.", + "createdDate": "2023-10-26 21:20:54Z", + "expirationDate": null, + "type": null } } } From c71b23415d0acfa81ca254f5bfd23b550a39e389 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 31 Oct 2023 07:47:30 -1000 Subject: [PATCH 09/12] Small review changes. --- Documentation/guides/AndroidMavenLibrary.md | 2 ++ .../Xamarin/Android/Xamarin.Android.Bindings.Maven.targets | 4 ++-- .../Utilities/MavenExtensions.cs | 7 +++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Documentation/guides/AndroidMavenLibrary.md b/Documentation/guides/AndroidMavenLibrary.md index b1f59c642e4..e1e22bc5659 100644 --- a/Documentation/guides/AndroidMavenLibrary.md +++ b/Documentation/guides/AndroidMavenLibrary.md @@ -21,6 +21,8 @@ This will do two things at build time: - Download the Java [artifact](https://central.sonatype.com/artifact/com.squareup.okhttp3/okhttp/4.9.3) with group id `com.squareup.okhttp3`, artifact id `okhttp`, and version `4.9.3` from [Maven Central](https://central.sonatype.com/) to a local cache (if not already cached). - Add the cached package to the .NET Android bindings build as an [``](https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/building-apps/build-items.md#androidlibrary). +Note that only the requested Java artifact is added to the .NET Android bindings build. Any artifact dependencies are not added. If the requested artifact has dependencies, they must be fulfilled individually. + ### Options `` defaults to using Maven Central, however it should support any Maven repository that does not require authentication. This can be controlled with the `Repository` attribute. diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets index ad2f2b0d92c..ba1efa14479 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets @@ -13,8 +13,8 @@ This file contains MSBuild targets used to enable @(AndroidMavenLibrary) support - $(HOME)\Library\Caches\MavenCacheDirectory\ - $(LocalAppData)\MavenCacheDirectory\ + $(HOME)/Library/Caches/dotnet-android/MavenCacheDirectory/ + $(LocalAppData)\dotnet-android\MavenCacheDirectory\ $(MavenCacheDirectory)\ diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs index 609d7b6e468..4fc7f031833 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MavenExtensions.cs @@ -18,6 +18,7 @@ namespace Xamarin.Android.Tasks; static class MavenExtensions { static readonly char [] separator = [':']; + static XmlSerializer pom_serializer = new XmlSerializer (typeof (Project)); /// /// Shortcut for !string.IsNullOrWhiteSpace (s) @@ -27,7 +28,7 @@ static class MavenExtensions // Helps to 'foreach' into a possibly null array public static T [] OrEmpty (this T []? value) { - return value ?? Enumerable.Empty ().ToArray (); + return value ?? Array.Empty (); } public static Artifact? ParseArtifact (string id, string version, TaskLoggingHelper log) @@ -48,10 +49,8 @@ public static Project ParsePom (string pomFile) { Project result = null; - var serializer = new XmlSerializer (typeof (Project)); - using (var sr = File.OpenRead (pomFile)) - result = (Project) serializer.Deserialize (new XmlTextReader (sr) { + result = (Project) pom_serializer.Deserialize (new XmlTextReader (sr) { Namespaces = false, }); From 4b5d5df247524c49dce9cf066003232f695d44b4 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 13 Nov 2023 14:52:10 -0500 Subject: [PATCH 10/12] Update Xamarin.Android.Bindings.Maven.targets Code formatting --- .../Xamarin/Android/Xamarin.Android.Bindings.Maven.targets | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets index ba1efa14479..c035c3a32dd 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets @@ -18,8 +18,10 @@ This file contains MSBuild targets used to enable @(AndroidMavenLibrary) support $(MavenCacheDirectory)\ - + From 183f67b048b8d1d5cd8847b354b11f2700c7a792 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 14 Nov 2023 08:30:57 -1000 Subject: [PATCH 11/12] Review feedback. --- .../Xamarin.Android.Bindings.Maven.targets | 11 ++++++----- .../{MavenDownloadTask.cs => MavenDownload.cs} | 2 +- ...nloadTaskTests.cs => MavenDownloadTests.cs} | 18 +++++++++--------- 3 files changed, 16 insertions(+), 15 deletions(-) rename src/Xamarin.Android.Build.Tasks/Tasks/{MavenDownloadTask.cs => MavenDownload.cs} (98%) rename src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/{MavenDownloadTaskTests.cs => MavenDownloadTests.cs} (95%) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets index c035c3a32dd..bcae5b4d88c 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets @@ -9,13 +9,14 @@ This file contains MSBuild targets used to enable @(AndroidMavenLibrary) support - + - - $(HOME)/Library/Caches/dotnet-android/MavenCacheDirectory/ - $(LocalAppData)\dotnet-android\MavenCacheDirectory\ - $(MavenCacheDirectory)\ + + $(LocalAppData)\dotnet-android\MavenCacheDirectory\ + $(HOME)/Library/Caches/dotnet-android/MavenCacheDirectory/ + $(HOME)/.cache/dotnet-android/MavenCacheDirectory/ + $([MSBuild]::EnsureTrailingSlash('$(MavenCacheDirectory)') "MDT"; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs similarity index 95% rename from src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs rename to src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs index f0fb985b705..5d9c6bda151 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTaskTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs @@ -8,13 +8,13 @@ using Task = System.Threading.Tasks.Task; namespace Xamarin.Android.Build.Tests; -public class MavenDownloadTaskTests +public class MavenDownloadTests { [Test] public async Task MissingVersionMetadata () { var engine = new MockBuildEngine (TestContext.Out, new List ()); - var task = new MavenDownloadTask { + var task = new MavenDownload { BuildEngine = engine, AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material:material", null)], }; @@ -29,7 +29,7 @@ public async Task MissingVersionMetadata () public async Task InvalidArtifactSpecification_WrongNumberOfParts () { var engine = new MockBuildEngine (TestContext.Out, new List ()); - var task = new MavenDownloadTask { + var task = new MavenDownload { BuildEngine = engine, AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material", "1.0.0")], }; @@ -44,7 +44,7 @@ public async Task InvalidArtifactSpecification_WrongNumberOfParts () public async Task InvalidArtifactSpecification_EmptyPart () { var engine = new MockBuildEngine (TestContext.Out, new List ()); - var task = new MavenDownloadTask { + var task = new MavenDownload { BuildEngine = engine, AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material: ", "1.0.0")], }; @@ -59,7 +59,7 @@ public async Task InvalidArtifactSpecification_EmptyPart () public async Task UnknownRepository () { var engine = new MockBuildEngine (TestContext.Out, new List ()); - var task = new MavenDownloadTask { + var task = new MavenDownload { BuildEngine = engine, AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material:material", "1.0.0", "bad-repo")], }; @@ -74,7 +74,7 @@ public async Task UnknownRepository () public async Task UnknownArtifact () { var engine = new MockBuildEngine (TestContext.Out, new List ()); - var task = new MavenDownloadTask { + var task = new MavenDownload { BuildEngine = engine, MavenCacheDirectory = Path.GetTempPath (), AndroidMavenLibraries = [CreateMavenTaskItem ("com.example:dummy", "1.0.0")], @@ -93,7 +93,7 @@ public async Task UnknownPom () try { var engine = new MockBuildEngine (TestContext.Out, new List ()); - var task = new MavenDownloadTask { + var task = new MavenDownload { BuildEngine = engine, MavenCacheDirectory = temp_cache_dir, AndroidMavenLibraries = [CreateMavenTaskItem ("com.example:dummy", "1.0.0")], @@ -121,7 +121,7 @@ public async Task MavenCentralSuccess () try { var engine = new MockBuildEngine (TestContext.Out, new List ()); - var task = new MavenDownloadTask { + var task = new MavenDownload { BuildEngine = engine, MavenCacheDirectory = temp_cache_dir, AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.auto.value:auto-value-annotations", "1.10.4")], @@ -152,7 +152,7 @@ public async Task MavenGoogleSuccess () try { var engine = new MockBuildEngine (TestContext.Out, new List ()); - var task = new MavenDownloadTask { + var task = new MavenDownload { BuildEngine = engine, MavenCacheDirectory = temp_cache_dir, AndroidMavenLibraries = [CreateMavenTaskItem ("androidx.core:core", "1.12.0", "Google")], From 2a0245b69393ce2e5f88ed2232929188c785d740 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 14 Nov 2023 10:28:24 -1000 Subject: [PATCH 12/12] Fix MSBuild error --- .../Android/Xamarin.Android.Bindings.Maven.targets | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets index bcae5b4d88c..e47e4b6bd93 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Maven.targets @@ -15,8 +15,8 @@ This file contains MSBuild targets used to enable @(AndroidMavenLibrary) support $(LocalAppData)\dotnet-android\MavenCacheDirectory\ $(HOME)/Library/Caches/dotnet-android/MavenCacheDirectory/ - $(HOME)/.cache/dotnet-android/MavenCacheDirectory/ - $([MSBuild]::EnsureTrailingSlash('$(MavenCacheDirectory)') + $(HOME)/.cache/dotnet-android/MavenCacheDirectory/ + $([MSBuild]::EnsureTrailingSlash('$(MavenCacheDirectory)')) - + - +