diff --git a/external/Java.Interop/tools/class-parse/Program.cs b/external/Java.Interop/tools/class-parse/Program.cs index bf3b2f17813..89335bf35c8 100644 --- a/external/Java.Interop/tools/class-parse/Program.cs +++ b/external/Java.Interop/tools/class-parse/Program.cs @@ -23,8 +23,10 @@ public static void Main (string[] args) int verbosity = 0; bool autorename = false; var outputFile = (string) null; + var referenceOutputFile = (string) null; string platform = null; var docsPaths = new List (); + var referenceFiles = new List (); var p = new OptionSet () { "usage: class-dump [-dump] FILES [@RESPONSE-FILES]", "", @@ -37,6 +39,12 @@ public static void Main (string[] args) { "o=", "Write output to {PATH}.", v => outputFile = v }, + { "reference=", + "Reference .class or .jar {FILE}.", + v => referenceFiles.Add (v) }, + { "reference-output=", + "Write the reference API to {PATH}.", + v => referenceOutputFile = v }, { "docspath=", "Documentation {PATH} for parameter fixup", doc => docsPaths.Add (doc) }, @@ -67,18 +75,32 @@ public static void Main (string[] args) } if (docsType) Console.WriteLine ("class-parse: --docstype is obsolete and no longer a valid option."); - var output = outputFile == null - ? Console.Out - : (TextWriter) new StreamWriter (outputFile, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false)); Log.OnLog = (t, v, m, a) => { Console.Error.WriteLine(m, a); }; - var globalClassPath = CreateClassPath (platform, docsPaths, autorename); + var globalClassPath = LoadClassPath (files, platform, docsPaths, autorename, dump, verbosity); + WriteOutput (globalClassPath, outputFile, dump); + if (referenceFiles.Count > 0) { + if (referenceOutputFile == null) { + Console.Error.WriteLine ("class-parse: --reference-output is required when using --reference."); + Environment.ExitCode = 1; + return; + } + var referenceClassPath = LoadClassPath (referenceFiles, platform, new List (), autoRename: false, dump: false, verbosity: verbosity); + WriteOutput (referenceClassPath, referenceOutputFile, dump: false); + } else if (referenceOutputFile != null && File.Exists (referenceOutputFile)) { + File.Delete (referenceOutputFile); + } + } + + static ClassPath LoadClassPath (IEnumerable files, string platform, List docsPaths, bool autoRename, bool dump, int verbosity) + { + var globalClassPath = CreateClassPath (platform, docsPaths, autoRename); var classPaths = new List (); foreach (var file in files) { try { if (ClassPath.IsJmodFile (file) || ClassPath.IsJarFile (file)) { - var cp = CreateClassPath (platform, docsPaths, autorename); + var cp = CreateClassPath (platform, docsPaths, autoRename); cp.Load (file); classPaths.Add (cp); continue; @@ -102,20 +124,31 @@ public static void Main (string[] args) foreach (var cp in classPaths) { globalClassPath.Add (cp, removeModules: !dump); } - if (!dump) { - globalClassPath.SaveXmlDescription (output); - } else { - bool first = true; - foreach (var c in globalClassPath.GetClassFiles ()) { - if (!first) { - output.WriteLine (); + return globalClassPath; + } + + static void WriteOutput (ClassPath classPath, string outputFile, bool dump) + { + var output = outputFile == null + ? Console.Out + : (TextWriter) new StreamWriter (outputFile, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false)); + try { + if (!dump) { + classPath.SaveXmlDescription (output); + } else { + bool first = true; + foreach (var c in classPath.GetClassFiles ()) { + if (!first) { + output.WriteLine (); + } + first = false; + DumpClassFile (c, output); } - first = false; - DumpClassFile (c, output); } + } finally { + if (outputFile != null) + output.Close (); } - if (outputFile != null) - output.Close (); } static ClassPath CreateClassPath (string platform, List docsPaths, bool autoRename) diff --git a/external/Java.Interop/tools/generator/CodeGenerator.cs b/external/Java.Interop/tools/generator/CodeGenerator.cs index fc04215f5ca..d21c4c163b6 100644 --- a/external/Java.Interop/tools/generator/CodeGenerator.cs +++ b/external/Java.Interop/tools/generator/CodeGenerator.cs @@ -110,7 +110,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve // Resolve types using Java.Interop.Tools.JavaTypeSystem if (is_classparse) { var output_xml = api_xml_adjuster_output ?? Path.Combine (Path.GetDirectoryName (filename), Path.GetFileName (filename) + ".adjusted"); - JavaTypeResolutionFixups.Fixup (filename, output_xml, resolver, references.Distinct ().ToArray (), resolverCache, options); + JavaTypeResolutionFixups.Fixup (filename, output_xml, resolver, references.Distinct ().ToArray (), options.JavaReferenceApiXml.ToArray (), resolverCache, options); if (only_xml_adjuster) return; @@ -120,6 +120,17 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve apiXmlFile = filename; } + foreach (var javaReference in options.JavaReferenceApiXml) { + var referenceApi = ApiXmlDocument.Load (javaReference, api_level, product_version); + if (referenceApi is null) + continue; + var referenceGens = XmlApiImporter.Parse (referenceApi.ApiDocument, opt); + if (referenceGens is null) + continue; + foreach (var referenceGen in referenceGens) + AddTypeToTable (opt, referenceGen); + } + foreach (var reference in references.Distinct ()) { try { Report.Verbose (0, "resolving assembly {0}.", reference); diff --git a/external/Java.Interop/tools/generator/CodeGeneratorOptions.cs b/external/Java.Interop/tools/generator/CodeGeneratorOptions.cs index 3995279ef6c..b6a564df0c7 100644 --- a/external/Java.Interop/tools/generator/CodeGeneratorOptions.cs +++ b/external/Java.Interop/tools/generator/CodeGeneratorOptions.cs @@ -20,6 +20,7 @@ public CodeGeneratorOptions () LibraryPaths = new Collection (); AnnotationsZipFiles = new Collection (); JavadocXmlFiles = new Collection (); + JavaReferenceApiXml = new Collection (); } public string ApiLevel {get; set;} @@ -30,6 +31,7 @@ public CodeGeneratorOptions () public Collection FixupFiles {get; private set;} public Collection LibraryPaths {get; private set;} public Collection JavadocXmlFiles {get; private set;} + public Collection JavaReferenceApiXml {get; private set;} public bool GlobalTypeNames {get; set;} public bool OnlyBindPublicTypes {get; set;} public string ApiDescriptionFile {get; set;} @@ -100,6 +102,9 @@ public static CodeGeneratorOptions Parse (string[] args) { "r|ref=", "{ASSEMBLY} to reference.", v => opts.AssemblyReferences.Add (v) }, + { "java-reference=", + "Java API XML {FILE} containing reference-only types.", + v => opts.JavaReferenceApiXml.Add (v) }, { "sdk-platform|api-level=", "SDK Platform {VERSION}/API level.", v => opts.ApiLevel = v }, diff --git a/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.Transformation/JavaTypeResolutionFixups.cs b/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.Transformation/JavaTypeResolutionFixups.cs index bf830145777..e8a3e59f433 100644 --- a/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.Transformation/JavaTypeResolutionFixups.cs +++ b/external/Java.Interop/tools/generator/Java.Interop.Tools.Generator.Transformation/JavaTypeResolutionFixups.cs @@ -15,12 +15,18 @@ public static class JavaTypeResolutionFixups // This fixup ensures all referenced Java types can be resolved, and // removes types and members that rely on unresolvable Java types. - public static void Fixup (string xmlFile, string outputXmlFile, DirectoryAssemblyResolver resolver, string [] references, TypeDefinitionCache cache, CodeGeneratorOptions opt) + public static void Fixup (string xmlFile, string outputXmlFile, DirectoryAssemblyResolver resolver, string [] references, string [] javaReferences, TypeDefinitionCache cache, CodeGeneratorOptions opt) { // Parse api.xml var type_collection = JavaXmlApiImporter.Parse (xmlFile); var options = new ApiImporterOptions (); + foreach (var javaReference in javaReferences) { + var referenceTypes = JavaXmlApiImporter.Parse (javaReference); + foreach (var type in referenceTypes.TypesFlattened.Values) + type_collection.AddReferencedType (type); + } + if (opt.CodeGenerationTarget == CodeGenerationTarget.JavaInterop1) { options.SupportedTypeMapAttributes.Clear (); options.SupportedTypeMapAttributes.Add ("Java.Interop.JniTypeSignatureAttribute"); diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.AvailableItems.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.AvailableItems.targets index 484356c5fd8..220a5f8ca3f 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.AvailableItems.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.AvailableItems.targets @@ -74,7 +74,9 @@ This item group populates the Build Action drop-down in IDEs. + <_AndroidReferenceLibraryProjectZip Include="@(AndroidLibrary)" Condition=" '%(AndroidLibrary.Extension)' == '.aar' and '%(AndroidLibrary.Bind)' != 'true' " /> + diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.ClassParse.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.ClassParse.targets index 604dc1ea93b..4e1b6e05063 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.ClassParse.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.ClassParse.targets @@ -13,10 +13,26 @@ This file is only used by binding projects. + + + <_ExportJarToXmlOutputs Remove="@(_ExportJarToXmlOutputs)" /> + <_JavaReferenceApiXml Remove="@(_JavaReferenceApiXml)" /> + <_ExportJarToXmlOutputs Include="$(ApiOutputFile)" /> + <_ExportJarToXmlOutputs + Include="$(ApiOutputFile).reference.class-parse" + Condition=" '@(EmbeddedReferenceJar->Count())' != '0' or '@(ReferenceJar->Count())' != '0' " + /> + <_JavaReferenceApiXml + Include="$(ApiOutputFile).reference.class-parse" + Condition=" '@(EmbeddedReferenceJar->Count())' != '0' or '@(ReferenceJar->Count())' != '0' " + /> + + + + Outputs="@(_ExportJarToXmlOutputs)"> @@ -35,6 +51,8 @@ This file is only used by binding projects. + diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index e627ff9c408..37047bc3904 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -102,6 +102,7 @@ It is shared between "legacy" binding projects and .NET 5 projects. CodegenTarget="$(AndroidCodegenTarget)" AndroidApiLevel="$(_AndroidApiLevel)" ApiXmlInput="$(ApiOutputFile)" + JavaReferenceApiXml="@(_JavaReferenceApiXml)" AnnotationsZipFiles="@(AnnotationsZip)" AssemblyName="$(AssemblyName)" JavadocVerbosity="$(AndroidJavadocVerbosity)" @@ -154,6 +155,7 @@ It is shared between "legacy" binding projects and .NET 5 projects. + @@ -181,6 +183,8 @@ It is shared between "legacy" binding projects and .NET 5 projects. + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AndroidLibraries.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AndroidLibraries.targets index 6d2d016dabe..676ad51b157 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AndroidLibraries.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AndroidLibraries.targets @@ -110,12 +110,16 @@ projects. diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ClassParse.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ClassParse.cs index ece680ed7a6..f8375eff8f2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ClassParse.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ClassParse.cs @@ -18,13 +18,17 @@ public class ClassParse : AndroidDotnetToolTask [Required] public ITaskItem[] SourceJars { get; set; } = []; + public string? ReferenceOutputFile { get; set; } + + public ITaskItem []? ReferenceJars { get; set; } + public ITaskItem []? DocumentationPaths { get; set; } protected override string GenerateCommandLineCommands () { var cmd = GetCommandLineBuilder (); - var responseFile = Path.Combine (Path.GetDirectoryName (OutputFile), "class-parse.rsp"); + var responseFile = Path.Combine (Path.GetDirectoryName (Path.GetFullPath (OutputFile)) ?? "", "class-parse.rsp"); Log.LogDebugMessage ("[class-parse] response file: {0}", responseFile); using (var sw = new StreamWriter (responseFile, append: false, encoding: Files.UTF8withoutBOM)) { @@ -34,6 +38,13 @@ protected override string GenerateCommandLineCommands () foreach (var doc in DocumentationPaths) WriteLine (sw, $"--docspath=\"{doc}\""); + if (!ReferenceOutputFile.IsNullOrEmpty ()) + WriteLine (sw, $"--reference-output=\"{ReferenceOutputFile}\""); + + if (ReferenceJars != null) + foreach (var reference in ReferenceJars) + WriteLine (sw, $"--reference=\"{reference}\""); + foreach (var doc in SourceJars) WriteLine (sw, $"\"{doc}\""); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ExtractJarsFromAar.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ExtractJarsFromAar.cs index f5fc112a8a0..7d34a66016b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ExtractJarsFromAar.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ExtractJarsFromAar.cs @@ -24,19 +24,36 @@ public class ExtractJarsFromAar : AndroidTask public string []? Libraries { get; set; } + [Required] + public string OutputReferenceJarsDirectory { get; set; } = ""; + + [Required] + public string OutputReferenceAnnotationsDirectory { get; set; } = ""; + + public string []? ReferenceLibraries { get; set; } + public override bool RunTask () { - if (Libraries == null || Libraries.Length == 0) - return true; - var memoryStream = MemoryStreamPool.Shared.Rent (); try { - var jars = new HashSet (StringComparer.OrdinalIgnoreCase); - var annotations = new HashSet (StringComparer.OrdinalIgnoreCase); - foreach (var library in Libraries) { + ExtractLibraries (Libraries, OutputJarsDirectory, OutputAnnotationsDirectory, memoryStream); + ExtractLibraries (ReferenceLibraries, OutputReferenceJarsDirectory, OutputReferenceAnnotationsDirectory, memoryStream); + } finally { + MemoryStreamPool.Shared.Return (memoryStream); + } + + return !Log.HasLoggedErrors; + } + + void ExtractLibraries (string []? libraries, string outputJarsDirectory, string outputAnnotationsDirectory, MemoryStream memoryStream) + { + var jars = new HashSet (StringComparer.OrdinalIgnoreCase); + var annotations = new HashSet (StringComparer.OrdinalIgnoreCase); + if (libraries != null) { + foreach (var library in libraries) { bool isAar = library.EndsWith (".aar", StringComparison.OrdinalIgnoreCase); - var jarOutputDirectory = Path.Combine (OutputJarsDirectory, Path.GetFileName (library)); - var annotationOutputDirectory = Path.Combine (OutputAnnotationsDirectory, Path.GetFileName (library)); + var jarOutputDirectory = Path.Combine (outputJarsDirectory, Path.GetFileName (library)); + var annotationOutputDirectory = Path.Combine (outputAnnotationsDirectory, Path.GetFileName (library)); using (var zip = MonoAndroidHelper.ReadZipFile (library)) { foreach (var entry in zip) { if (entry.IsDirectory) @@ -63,13 +80,9 @@ public override bool RunTask () } } } - DeleteUnknownFiles (OutputJarsDirectory, jars); - DeleteUnknownFiles (OutputAnnotationsDirectory, annotations); - } finally { - MemoryStreamPool.Shared.Return (memoryStream); } - - return !Log.HasLoggedErrors; + DeleteUnknownFiles (outputJarsDirectory, jars); + DeleteUnknownFiles (outputAnnotationsDirectory, annotations); } bool IsUnderDirectory (string resolvedPath, string targetDirectory, string entryName, string archivePath) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs index e2bc343b358..64594d09ffa 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs @@ -64,6 +64,7 @@ public class BindingsGenerator : AndroidDotnetToolTask public ITaskItem[]? JavadocXml { get; set; } public string? JavadocVerbosity { get; set; } + public ITaskItem []? JavaReferenceApiXml { get; set; } private List> transform_files = new List> (); @@ -187,6 +188,9 @@ protected override string GenerateCommandLineCommands () if (ReferencedManagedLibraries != null) foreach (var lib in ReferencedManagedLibraries) WriteLine (sw, $"--ref=\"{Path.GetFullPath (lib.ItemSpec)}\""); + if (JavaReferenceApiXml != null) + foreach (var reference in JavaReferenceApiXml) + WriteLine (sw, $"--java-reference=\"{Path.GetFullPath (reference.ItemSpec)}\""); if (AnnotationsZipFiles != null) foreach (var zip in AnnotationsZipFiles) WriteLine (sw, $"--annotations=\"{Path.GetFullPath (zip.ItemSpec)}\""); 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 436c2852a8b..b3b965a027e 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 @@ -520,7 +520,7 @@ public void OnUpdate (Java.Lang.Object p0) } [Test] - public void AndroidLibraryPackFalseExcludesJarFromAar ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime) + public void AndroidLibraryPackMetadataIsPreservedInNuGet ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime) { const bool isRelease = true; if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { @@ -542,23 +542,97 @@ public void AndroidLibraryPackFalseExcludesJarFromAar ([Values (AndroidRuntime.C }); using (var bindingBuilder = CreateDllBuilder ()) { - Assert.IsTrue (bindingBuilder.Build (binding), "binding build should have succeeded"); + bindingBuilder.Target = "Pack"; + Assert.IsTrue (bindingBuilder.Build (binding), "`Pack` should succeed"); + + var nupkgPath = Path.Combine (Root, bindingBuilder.ProjectDirectory, binding.OutputPath, "UnnamedProject.1.0.0.nupkg"); + FileAssert.Exists (nupkgPath); + using (var nupkg = ZipArchive.Open (nupkgPath, FileMode.Open)) { + var aarEntry = nupkg.Single (entry => entry.FullName.EndsWith ("/UnnamedProject.aar", StringComparison.Ordinal)); + using var aarStream = new MemoryStream (); + aarEntry.Extract (aarStream); + aarStream.Position = 0; + using var aar = ZipArchive.Open (aarStream); + Assert.AreEqual (1, aar.Count (entry => entry.FullName.StartsWith ("libs/", StringComparison.Ordinal) && entry.FullName.EndsWith (".jar", StringComparison.Ordinal)), + "The generated AAR should contain only the Bind='false', Pack='true' JAR."); + } + } + } - // Check that the AAR file was created - var aarPath = Path.Combine (Root, bindingBuilder.ProjectDirectory, binding.OutputPath, "UnnamedProject.aar"); - FileAssert.Exists (aarPath); + [Test] + public void BindFalseAndroidLibraryResolvesGeneratorTypes ( + [Values (".jar", ".aar")] string extension, + [Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime) + { + const bool isRelease = true; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } - // Extract and examine AAR contents - using (var aar = ZipArchive.Open (aarPath, FileMode.Open)) { - // test-pack-false.jar should NOT be in the AAR because Pack='false' - var packFalseEntry = aar.Where (e => e.FullName.Contains ("test-pack-false")).FirstOrDefault (); - Assert.IsNull (packFalseEntry, "Jar with Pack='false' should not be included in AAR"); + var dependency = extension == ".aar" ? CreateAar (ResourceData.JavaSourceJarTestJar) : ResourceData.JavaSourceJarTestJar; + var binding = new XamarinAndroidBindingProject { + IsRelease = isRelease, + AndroidClassParser = "class-parse", + Sources = { + new BuildItem.Source ("JavaSourceJarTest.cs") { + TextContent = () => @" +namespace Com.Xamarin.Android.Test.Msbuildtest { + [global::Android.Runtime.Register (""com/xamarin/android/test/msbuildtest/JavaSourceJarTest"", DoNotGenerateAcw = true)] + public class JavaSourceJarTest : global::Java.Lang.Object { + } +} +", + }, + }, + AndroidJavaSources = { + new AndroidItem.AndroidJavaSource ("UsesDependency.java") { + Encoding = Encoding.ASCII, + TextContent = () => @" +package com.example; - // test-pack-true.jar should be in the AAR (default Pack='true') - var packTrueEntry = aar.Where (e => e.FullName.Contains ("test-pack-true") || e.FullName.StartsWith ("libs/")).FirstOrDefault (); - Assert.IsNotNull (packTrueEntry, "Jar with Pack='true' (default) should be included in AAR"); - } +import com.xamarin.android.test.msbuildtest.JavaSourceJarTest; + +public class UsesDependency { + public JavaSourceJarTest echo (JavaSourceJarTest value) { + return value; + } +} +", + Metadata = { { "Bind", "true" } }, + }, + }, + }; + binding.SetRuntime (runtime); + binding.OtherBuildItems.Add (new AndroidItem.AndroidLibrary ($"dependency{extension}") { + BinaryContent = () => dependency, + MetadataValues = "Bind=false", + }); + using var builder = CreateDllBuilder (); + Assert.IsTrue (builder.Build (binding), "Binding build should have succeeded."); + var apiXml = builder.Output.GetIntermediaryPath ("api.xml"); + FileAssert.Exists (apiXml); + var referenceApiXml = $"{apiXml}.reference.class-parse"; + FileAssert.Exists (referenceApiXml); + File.Delete (referenceApiXml); + Assert.IsTrue (builder.Build (binding, doNotCleanupOnUpdate: true, saveProject: false), "Missing reference API recovery build should have succeeded."); + FileAssert.Exists (referenceApiXml); + StringAssert.Contains ("com.xamarin.android.test.msbuildtest.JavaSourceJarTest", File.ReadAllText (apiXml), + "Bind='false' dependency types should be available while resolving the generated API."); + var generatedSourceDirectory = Path.Combine (Root, builder.ProjectDirectory, binding.IntermediateOutputPath, "generated", "src"); + var generatedSources = Directory.EnumerateFiles (generatedSourceDirectory, "*.cs", SearchOption.AllDirectories).ToArray (); + Assert.IsTrue (generatedSources.Any (source => File.ReadAllText (source).Contains (" Echo (", StringComparison.Ordinal)), + "Members that use Bind='false' dependency types should generate managed bindings."); + Assert.IsFalse (generatedSources.Any (source => Path.GetFileName (source).Contains ("JavaSourceJarTest", StringComparison.Ordinal)), + "Bind='false' dependency types should not generate managed bindings."); + } + + static byte [] CreateAar (byte [] classesJar) + { + using var stream = new MemoryStream (); + using (var aar = ZipArchive.Open (stream)) { + aar.AddStream (new MemoryStream (classesJar), "classes.jar"); } + return stream.ToArray (); } [Test] diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/AndroidDotnetToolTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/AndroidDotnetToolTests.cs index d6d03315ad2..adab324a77e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/AndroidDotnetToolTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/AndroidDotnetToolTests.cs @@ -43,6 +43,32 @@ public void ShouldUseFullToolPath () Assert.True (classParseTask.Execute (), "Task should have succeeded."); Assert.IsTrue (messages.Any (m => m.Message.StartsWith (dotnetPath)), "Task did not use expected tool path."); } + + [Test] + public void ClassParseWritesReferenceArguments () + { + var outputDirectory = Path.Combine (Root, "temp", TestName); + Directory.CreateDirectory (outputDirectory); + TestOutputDirectories [TestContext.CurrentContext.Test.ID] = outputDirectory; + var output = Path.Combine (outputDirectory, "api.xml"); + var referenceOutput = Path.Combine (outputDirectory, "reference-api.xml"); + var sourceJar = Path.Combine (outputDirectory, "source.jar"); + var referenceJar = Path.Combine (outputDirectory, "reference.jar"); + var task = new ClassParseArgumentsTestTask { + BuildEngine = engine, + OutputFile = output, + ReferenceOutputFile = referenceOutput, + ReferenceJars = [new TaskItem (referenceJar)], + SourceJars = [new TaskItem (sourceJar)], + }; + + task.GenerateArguments (); + + var responseFile = File.ReadAllLines (Path.Combine (outputDirectory, "class-parse.rsp")); + Assert.That (responseFile, Does.Contain ($"--reference-output=\"{referenceOutput}\"")); + Assert.That (responseFile, Does.Contain ($"--reference=\"{referenceJar}\"")); + Assert.That (responseFile, Does.Contain ($"\"{sourceJar}\"")); + } } public class ClassParseTestTask : AndroidDotnetToolTask @@ -53,4 +79,12 @@ protected override string GenerateCommandLineCommands () return GetCommandLineBuilder ().ToString (); } } + + public class ClassParseArgumentsTestTask : ClassParse + { + public string GenerateArguments () + { + return GenerateCommandLineCommands (); + } + } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ExtractJarsFromAarTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ExtractJarsFromAarTests.cs index e089ccd9dc4..39e29f596b5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ExtractJarsFromAarTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ExtractJarsFromAarTests.cs @@ -41,6 +41,8 @@ public void PathTraversalInJarEntry () BuildEngine = engine, OutputJarsDirectory = jarOutputDir, OutputAnnotationsDirectory = annotationOutputDir, + OutputReferenceJarsDirectory = Path.Combine (path, "reference-jars"), + OutputReferenceAnnotationsDirectory = Path.Combine (path, "reference-annotations"), Libraries = [aarPath], }; @@ -65,6 +67,8 @@ public void PathTraversalInAnnotationsEntry () BuildEngine = engine, OutputJarsDirectory = jarOutputDir, OutputAnnotationsDirectory = annotationOutputDir, + OutputReferenceJarsDirectory = Path.Combine (path, "reference-jars"), + OutputReferenceAnnotationsDirectory = Path.Combine (path, "reference-annotations"), Libraries = [aarPath], }; @@ -85,6 +89,8 @@ public void ValidJarEntry () BuildEngine = engine, OutputJarsDirectory = jarOutputDir, OutputAnnotationsDirectory = annotationOutputDir, + OutputReferenceJarsDirectory = Path.Combine (path, "reference-jars"), + OutputReferenceAnnotationsDirectory = Path.Combine (path, "reference-annotations"), Libraries = [aarPath], }; @@ -92,9 +98,43 @@ public void ValidJarEntry () Assert.IsEmpty (errors, "No errors should be logged."); } + [Test] + public void ExtractsBoundAndReferenceLibraries () + { + var boundAar = CreateAarWithEntry ("bound.aar", "classes.jar"); + var referenceAar = CreateAarWithEntry ("reference.aar", "libs/helper.jar"); + var jarOutputDir = Path.Combine (path, "jars"); + var annotationOutputDir = Path.Combine (path, "annotations"); + var referenceJarOutputDir = Path.Combine (path, "reference-jars"); + var referenceAnnotationOutputDir = Path.Combine (path, "reference-annotations"); + var task = new ExtractJarsFromAar { + BuildEngine = engine, + OutputJarsDirectory = jarOutputDir, + OutputAnnotationsDirectory = annotationOutputDir, + Libraries = [boundAar], + OutputReferenceJarsDirectory = referenceJarOutputDir, + OutputReferenceAnnotationsDirectory = referenceAnnotationOutputDir, + ReferenceLibraries = [referenceAar], + }; + + Assert.IsTrue (task.Execute (), "Task should extract both library categories."); + Assert.IsTrue (File.Exists (Path.Combine (jarOutputDir, "bound.aar", "classes.jar"))); + var extractedReference = Path.Combine (referenceJarOutputDir, "reference.aar", "libs", "helper.jar"); + Assert.IsTrue (File.Exists (extractedReference)); + + task.ReferenceLibraries = []; + Assert.IsTrue (task.Execute (), "Task should clean an empty reference category."); + Assert.IsFalse (File.Exists (extractedReference), "Stale reference JAR should be deleted."); + } + string CreateAarWithEntry (string entryName) { - var aarPath = Path.Combine (path, "test.aar"); + return CreateAarWithEntry ("test.aar", entryName); + } + + string CreateAarWithEntry (string fileName, string entryName) + { + var aarPath = Path.Combine (path, fileName); using (var stream = new FileStream (aarPath, FileMode.Create)) using (var archive = new ZipArchive (stream, ZipArchiveMode.Create)) { var entry = archive.CreateEntry (entryName);