From b18bfdf9b32a1c16e87c9f65adf61f217ca779f5 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 11 Apr 2023 23:45:45 -0400 Subject: [PATCH 1/2] [class-parse] support Module AttributeInfo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/xamarin/java.interop/issues/1096 Context: https://stackoverflow.com/questions/57358750/module-info-class-file-is-different-in-the-module-jar-file-and-compiled-module-i Context: 678c4bd2c838141be0e18b23690da8dcdd94e0ec JDK 9 adds support for [modules][0], which are (kinda sorta) like .NET Assemblies: modules can depend upon other modules, export packages, etc. In particular: > **exports and exports…to.** An exports module directive specifies > one of the module’s packages whose `public` types (and their nested > `public` and `protected` types) should be accessible to code in all > other modules. This allows an equivalent to the [C# `internal` access modifier][1]: `public` types in a *non-`export`ed package* should be treated as "internal", while `public` types in an `export`ed package are "fully public". Update `Xamarin.Android.Tools.Bytecode.dll` to extract the module- related information, the update `XmlClassDeclarationBuilder` so that it updates all `public` types *outside* of the "exported" packages to have a visibility of `kotlin-internal`. Why a `//*/@visibility` value of `kotlin-internal`? From a [suggestion][2] for the commit message of 678c4bd2, which was sadly overlooked in the final merge: > Note: we introduce and use a new `//*/@visibility` value of > `kotlin-internal` because `internal` is an *existing* value that may > be used in `Metadata.xml` files, e.g. making `public` API `internal` > so that it can still be used in the binding, but isn't *public*. If we use `internal`, *those types are still bound*, it's just that the bound types have C# `internal` visibility, while we *want* them to be *skipped entirely*. A visibility value of `kotlin-internal` allows us to skip them, which is desired. `tests/Xamarin.Android.Tools.Bytecode-Tests` has been updated to: 1. Contain a `module-info.java`, which declares a `com.xamarin` module. 2. Add a new `com.xamarin.internal.PublicClassNotInModuleExports` type which is *not* in the `com.xamarin` package, but instead a *nested* package. The type is `public`. 3. Build a `xatb.jar` artifact This makes for a simple one-off test: % dotnet build tests/Xamarin.Android.Tools.Bytecode-Tests/*.csproj % dotnet build tools/class-parse/*.csproj % dotnet bin/Debug-net7.0/class-parse.dll \ tests/Xamarin.Android.Tools.Bytecode-Tests/obj/Debug-net7.0/xatb.jar … Note that `com.xamarin.internal.PublicClassNotInModuleExports` is now shown as `kotlin-internal` instead of `public`. Aside, a discovered oddity: `jar cf …` *modifies* `module-info.class`, adding a `ModulePackages` attribute! (Specifically, if you compare the "on-disk" `module-info.class` to the one within `tests/Xamarin.Android.Tools.Bytecode-Tests/obj/$(Configuration)/xatb.jar`, they differ in size!) [0]: https://www.oracle.com/corporate/features/understanding-java-9-modules.html [1]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/internal [2]: https://github.com/xamarin/java.interop/pull/793#issuecomment-777762450 --- .../AttributeInfo.cs | 119 ++++++++++++ .../ClassFile.cs | 15 +- .../ClassPath.cs | 38 +++- .../Kotlin/KotlinFixups.cs | 2 +- src/Xamarin.Android.Tools.Bytecode/Modules.cs | 172 ++++++++++++++++++ .../Xamarin.Android.Tools.Bytecode.csproj | 2 +- .../XmlClassDeclarationBuilder.cs | 2 + .../ExpectedTypeDeclaration.cs | 2 +- .../ModuleInfoTests.cs | 45 +++++ ...amarin.Android.Tools.Bytecode-Tests.csproj | 1 + ...marin.Android.Tools.Bytecode-Tests.targets | 14 +- .../PublicClassNotInModuleExports.java | 4 + .../java/com/xamarin/module-info.java | 4 + 13 files changed, 410 insertions(+), 10 deletions(-) create mode 100644 src/Xamarin.Android.Tools.Bytecode/Modules.cs create mode 100644 tests/Xamarin.Android.Tools.Bytecode-Tests/ModuleInfoTests.cs create mode 100644 tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/internal/PublicClassNotInModuleExports.java create mode 100644 tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/module-info.java diff --git a/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs b/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs index 4dea58c21..85a8387b8 100644 --- a/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs +++ b/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs @@ -44,6 +44,8 @@ public class AttributeInfo { public const string InnerClasses = "InnerClasses"; public const string LocalVariableTable = "LocalVariableTable"; public const string MethodParameters = "MethodParameters"; + public const string Module = "Module"; + public const string ModulePackages = "ModulePackages"; public const string Signature = "Signature"; public const string SourceFile = "SourceFile"; public const string StackMapTable = "StackMapTable"; @@ -79,6 +81,8 @@ public string Name { { typeof (InnerClassesAttribute), InnerClasses }, { typeof (LocalVariableTableAttribute), LocalVariableTable }, { typeof (MethodParametersAttribute), MethodParameters }, + { typeof (ModuleAttribute), Module }, + { typeof (ModulePackagesAttribute), ModulePackages }, { typeof (RuntimeVisibleAnnotationsAttribute), RuntimeVisibleAnnotations }, { typeof (RuntimeInvisibleAnnotationsAttribute), RuntimeInvisibleAnnotations }, { typeof (SignatureAttribute), Signature }, @@ -98,6 +102,7 @@ internal static string GetAttributeName() public static AttributeInfo CreateFromStream (ConstantPool constantPool, Stream stream) { var nameIndex = stream.ReadNetworkUInt16 (); + var constant = constantPool [nameIndex]; var name = ((ConstantPoolUtf8Item) constantPool [nameIndex]).Value; var attr = CreateAttribute (name, constantPool, nameIndex, stream); return attr; @@ -114,6 +119,8 @@ static AttributeInfo CreateAttribute (string name, ConstantPool constantPool, us case InnerClasses: return new InnerClassesAttribute (constantPool, nameIndex, stream); case LocalVariableTable: return new LocalVariableTableAttribute (constantPool, nameIndex, stream); case MethodParameters: return new MethodParametersAttribute (constantPool, nameIndex, stream); + case Module: return new ModuleAttribute (constantPool, nameIndex, stream); + case ModulePackages: return new ModulePackagesAttribute (constantPool, nameIndex, stream); case RuntimeVisibleAnnotations: return new RuntimeVisibleAnnotationsAttribute (constantPool, nameIndex, stream); case RuntimeInvisibleAnnotations: return new RuntimeInvisibleAnnotationsAttribute (constantPool, nameIndex, stream); case RuntimeInvisibleParameterAnnotations: return new RuntimeInvisibleParameterAnnotationsAttribute (constantPool, nameIndex, stream); @@ -503,6 +510,118 @@ public override string ToString () } } + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.25 + public sealed class ModuleAttribute : AttributeInfo + { + ushort moduleNameIndex; + public string ModuleName { + get {return ((ConstantPoolModuleItem) ConstantPool [moduleNameIndex]).Name.Value;} + } + + public ModuleFlags ModuleFlags { get; private set; } + + ushort moduleVersionIndex; + public string? ModuleVersion { + get {return ((ConstantPoolUtf8Item) ConstantPool [moduleVersionIndex])?.Value;} + } + + public Collection Requires {get;} = new (); + public Collection Exports {get;} = new (); + public Collection Opens {get;} = new (); + public Collection Uses {get;} = new (); + public Collection Provides {get;} = new (); + + public ModuleAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream) + : base (constantPool, nameIndex, stream) + { + var attribute_length = stream.ReadNetworkUInt32 (); + + moduleNameIndex = stream.ReadNetworkUInt16 (); + ModuleFlags = (ModuleFlags) stream.ReadNetworkUInt16 (); + moduleVersionIndex = stream.ReadNetworkUInt16 (); + + var requires_count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < requires_count; ++i) { + Requires.Add (new ModuleRequiresInfo (constantPool, stream)); + } + + var exports_count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < exports_count; ++i) { + Exports.Add (new ModuleExportsPackageInfo (constantPool, stream)); + } + + var opens_count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < opens_count; ++i) { + Opens.Add (new ModuleOpensPackageInfo (constantPool, stream)); + } + + var uses_count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < uses_count; ++i) { + var uses_index = stream.ReadNetworkUInt16 (); + Uses.Add ((ConstantPoolClassItem) constantPool [uses_index]); + } + + var provides_count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < provides_count; ++i) { + Provides.Add (new ModuleProvidesInfo (constantPool, stream)); + } + } + + public override string ToString () + { + var s = new StringBuilder () + .Append ("Module(").AppendLine () + .Append (" ").Append (nameof (ModuleName)).Append ("='").Append (ModuleName).AppendLine ("', ") + .Append (" ").Append (nameof (ModuleVersion)).Append ("='").Append (ModuleVersion).Append ("'"); + AppendString (s, nameof (Requires), Requires); + AppendString (s, nameof (Exports), Exports); + AppendString (s, nameof (Opens), Opens); + AppendString (s, nameof (Uses), Uses.Select (u => $"UsesService({u.Name})").ToList ()); + AppendString (s, nameof (Provides), Provides); + s.Append (")"); + + return s.ToString (); + } + + static StringBuilder AppendString (StringBuilder s, string collectionName, IList items) + { + if (items.Count == 0) { + return s; + } + s.AppendLine (","); + s.Append (" ").Append (collectionName).AppendLine ("={"); + s.Append (" ").Append (items [0]); + for (int i = 1; i < items.Count; ++i) { + s.AppendLine (","); + s.Append (" "); + s.Append (items [i]); + } + return s.Append ("}"); + } + } + + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.26 + public sealed class ModulePackagesAttribute : AttributeInfo { + public Collection Packages {get;} = new (); + + public ModulePackagesAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream) + : base (constantPool, nameIndex, stream) + { + var attribute_length = stream.ReadNetworkUInt32 (); + + var package_count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < package_count; ++i) { + var package_index = stream.ReadNetworkUInt16 (); + Packages.Add ((ConstantPoolPackageItem) constantPool [package_index]); + } + } + + public override string ToString () + { + return $"ModulePackages({{{string.Join (", ", Packages.Select (p => p.Name.Value))}}})"; + } + } + // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16 public sealed class RuntimeVisibleAnnotationsAttribute : AttributeInfo { diff --git a/src/Xamarin.Android.Tools.Bytecode/ClassFile.cs b/src/Xamarin.Android.Tools.Bytecode/ClassFile.cs index 830581baf..ebd012291 100644 --- a/src/Xamarin.Android.Tools.Bytecode/ClassFile.cs +++ b/src/Xamarin.Android.Tools.Bytecode/ClassFile.cs @@ -48,8 +48,15 @@ public ClassFile (Stream stream) Attributes = new AttributeCollection (ConstantPool, stream); int e = stream.ReadByte (); - if (e >= 0) - throw new BadImageFormatException ("Stream has trailing data?!"); + if (e >= 0) { + var trailing = new System.Text.StringBuilder (); + trailing.AppendFormat ("{0:x2}", (byte) e); + while ((e = stream.ReadByte ()) >= 0) { + trailing.Append (" "); + trailing.AppendFormat ("{0:x2}", (byte) e); + } + throw new BadImageFormatException ($"Stream has trailing data?! {{{trailing}}}"); + } } public static bool IsClassFile (Stream stream) @@ -213,6 +220,10 @@ public enum ClassAccessFlags { Synthetic = 0x1000, Annotation = 0x2000, Enum = 0x4000, + Module = 0x8000, + + // This is not a real Java ClassAccessFlags, it is used to denote non-exported public types + Internal = 0x10000000, } } diff --git a/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs b/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs index b840780fa..f4ddecfa7 100644 --- a/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs +++ b/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs @@ -88,9 +88,6 @@ static bool ShouldLoadEntry (ZipArchiveEntry entry) if (entry.Length == 0) return false; - if (entry.Name == "module-info.class") - return false; - if (entry.Name.EndsWith (".jnilib", StringComparison.OrdinalIgnoreCase)) return false; @@ -360,6 +357,7 @@ public XElement ToXElement () FixUpParametersFromClasses (); KotlinFixups.Fixup (classFiles); + FixupModuleVisibility (); var packagesDictionary = GetPackages (); var api = new XElement ("api", @@ -375,6 +373,40 @@ packagesDictionary [p].OrderBy (c => c.ThisClass.Name.Value, StringComparer.Ordi return api; } + void FixupModuleVisibility () + { + var publicPackages = new HashSet (); + + var moduleFiles = classFiles.Where (c => c.AccessFlags == ClassAccessFlags.Module) + .ToList (); + if (moduleFiles.Count == 0) { + return; + } + foreach (var moduleFile in moduleFiles) { + classFiles.Remove (moduleFile); + foreach (var moduleAttr in moduleFile.Attributes.OfType ()) { + foreach (var export in moduleAttr.Exports) { + publicPackages.Add (export.Exports); + } + } + } + + foreach (var c in classFiles) { + if (!c.AccessFlags.HasFlag (ClassAccessFlags.Public)) { + continue; + } + var jniName = c.ThisClass.Name.Value; + var packageEnd = jniName.LastIndexOf ('/'); + if (packageEnd < 0) { + continue; + } + var package = jniName.Substring (0, packageEnd); + if (!publicPackages.Contains (package)) { + c.AccessFlags = KotlinFixups.SetVisibility (c.AccessFlags, ClassAccessFlags.Internal); + } + } + } + public void SaveXmlDescription (string fileName) { var encoding = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false); diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs index c2b556e57..0ad357d84 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs @@ -102,7 +102,7 @@ static void FixupClassVisibility (ClassFile klass, KotlinClass metadata) } // Passing null for 'newVisibility' parameter means 'package-private' - static ClassAccessFlags SetVisibility (ClassAccessFlags existing, ClassAccessFlags? newVisibility) + internal static ClassAccessFlags SetVisibility (ClassAccessFlags existing, ClassAccessFlags? newVisibility) { // First we need to remove any existing visibility flags, // without modifying other flags like Abstract diff --git a/src/Xamarin.Android.Tools.Bytecode/Modules.cs b/src/Xamarin.Android.Tools.Bytecode/Modules.cs new file mode 100644 index 000000000..2371eb6d5 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Modules.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamarin.Android.Tools.Bytecode { + + public enum ModuleFlags { + Open = 0x0020, + Synthetic = 0x1000, + Mandated = 0x8000, + } + + public enum ModuleRequiresInfoFlags { + Transitive = 0x0020, + StaticPhase = 0x0040, + Synthetic = 0x1000, + Mandated = 0x8000, + } + + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.25 + public sealed class ModuleRequiresInfo { + ushort requiresIndex; + ushort requiresVersionIndex; + + + public ConstantPool ConstantPool {get; private set;} + public ModuleRequiresInfoFlags RequiresFlags {get; private set;} + + public string Requires => + ((ConstantPoolModuleItem) ConstantPool [requiresIndex]).Name.Value; + public string RequiresVersion => + ((ConstantPoolUtf8Item) ConstantPool [requiresVersionIndex]).Value; + + public ModuleRequiresInfo (ConstantPool constantPool, Stream stream) + { + ConstantPool = constantPool; + + requiresIndex = stream.ReadNetworkUInt16 (); + RequiresFlags = (ModuleRequiresInfoFlags) stream.ReadNetworkUInt16 (); + requiresVersionIndex = stream.ReadNetworkUInt16 (); + } + + public override string ToString () + { + return $"Requires({Requires}, Version={RequiresVersion}, Flags={RequiresFlags})"; + } + } + + public enum ModuleExportsPackageInfoFlags { + Synthetic = 0x1000, + Mandated = 0x8000, + } + + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.25 + public sealed class ModuleExportsPackageInfo { + ushort exportsIndex; + public string Exports => + ((ConstantPoolPackageItem) ConstantPool [exportsIndex]).Name.Value; + + public ConstantPool ConstantPool {get; private set;} + public ModuleExportsPackageInfoFlags Flags {get; private set;} + public Collection ExportsTo {get;} = new (); + + + public ModuleExportsPackageInfo (ConstantPool constantPool, Stream stream) + { + ConstantPool = constantPool; + + exportsIndex = stream.ReadNetworkUInt16 (); + Flags = (ModuleExportsPackageInfoFlags) stream.ReadNetworkUInt16 (); + + var count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < count; ++i) { + var exports_to_index = stream.ReadNetworkUInt16 (); + ExportsTo.Add ((ConstantPoolModuleItem) constantPool [exports_to_index]); + } + } + + public override string ToString () + { + var s = new StringBuilder () + .Append ("ExportsPackage(Name=\"").Append (Exports).Append ("\""); + if (Flags != 0) { + s.Append (", Flags=").Append (Flags); + } + if (ExportsTo.Count > 0) { + s.Append (", To={"); + s.Append (ExportsTo [0].Name.Value); + for (int i = 1; i < ExportsTo.Count; ++i) { + s.Append (", "); + s.Append (ExportsTo [i].Name.Value); + } + s.Append ("}"); + } + s.Append (")"); + return s.ToString (); + } + } + + + public enum ModuleOpensPackageInfoFlags { + Synthetic = 0x1000, + Mandated = 0x8000, + } + + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.25 + public sealed class ModuleOpensPackageInfo { + ushort opensIndex; + + public ConstantPool ConstantPool {get; private set;} + + public ModuleOpensPackageInfoFlags Flags {get; private set;} + + public Collection OpensTo {get;} = new (); + public string Opens => + ((ConstantPoolPackageItem) ConstantPool [opensIndex]).Name.Value; + + public ModuleOpensPackageInfo (ConstantPool constantPool, Stream stream) + { + ConstantPool = constantPool; + + opensIndex = stream.ReadNetworkUInt16 (); + Flags = (ModuleOpensPackageInfoFlags) stream.ReadNetworkUInt16 (); + + var count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < count; ++i) { + var opens_to_index = stream.ReadNetworkUInt16 (); + OpensTo.Add ((ConstantPoolModuleItem) constantPool [opens_to_index]); + } + } + + public override string ToString () + { + var to = string.Join (", ", OpensTo.Select (e => e.Name.Value)); + return $"Opens({Opens}, Flags={Flags}, To={to})"; + } + } + + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.25 + public sealed class ModuleProvidesInfo { + ushort providesIndex; + + public string Provides => + ((ConstantPoolClassItem) ConstantPool [providesIndex]).Name.Value; + + public ConstantPool ConstantPool {get; private set;} + + public Collection ProvidesWith {get;} = new (); + + public ModuleProvidesInfo (ConstantPool constantPool, Stream stream) + { + ConstantPool = constantPool; + + providesIndex = stream.ReadNetworkUInt16 (); + + var count = stream.ReadNetworkUInt16 (); + for (int i = 0; i < count; ++i) { + var provides_with_index = stream.ReadNetworkUInt16 (); + ProvidesWith.Add ((ConstantPoolClassItem) constantPool [provides_with_index]); + } + } + + public override string ToString () + { + var with = string.Join (", ", ProvidesWith.Select (e => e.Name.Value)); + return $"Service(ServiceInterface={Provides}, ServiceImplementations={{{with}}})"; + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj b/src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj index becbe89fb..83e059975 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj +++ b/src/Xamarin.Android.Tools.Bytecode/Xamarin.Android.Tools.Bytecode.csproj @@ -2,7 +2,7 @@ netstandard2.0;$(DotNetTargetFramework) - 8.0 + 10.0 true ..\..\product.snk INTERNAL_NULLABLE_ATTRIBUTES diff --git a/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs b/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs index ef0c8a381..5a581162f 100644 --- a/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs +++ b/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs @@ -110,6 +110,8 @@ static string GetVisibility (ClassAccessFlags accessFlags) return "protected"; if ((accessFlags & ClassAccessFlags.Private) != 0) return "private"; + if (accessFlags.HasFlag (ClassAccessFlags.Internal)) + return "kotlin-internal"; return ""; } diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/ExpectedTypeDeclaration.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/ExpectedTypeDeclaration.cs index 98447adbf..d45cf6d4b 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/ExpectedTypeDeclaration.cs +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/ExpectedTypeDeclaration.cs @@ -29,7 +29,7 @@ public void Assert (ClassFile classDeclaration) NAssert.AreEqual (ConstantPoolCount, classDeclaration.ConstantPool.Count, FullName + " ConstantPool Count"); NAssert.AreEqual (AccessFlags, classDeclaration.AccessFlags, FullName + " AccessFlags"); NAssert.AreEqual (FullName, classDeclaration.ThisClass.Name.Value, FullName + " Name"); - NAssert.AreEqual (Superclass.BinaryName, classDeclaration.SuperClass.Name.Value, FullName + " SuperClass Name"); + NAssert.AreEqual (Superclass?.BinaryName, classDeclaration?.SuperClass?.Name?.Value, FullName + " SuperClass Name"); NAssert.AreEqual (Deprecated, classDeclaration.Attributes.Get () != null, FullName + " Deprecated"); diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/ModuleInfoTests.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/ModuleInfoTests.cs new file mode 100644 index 000000000..f4019c831 --- /dev/null +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/ModuleInfoTests.cs @@ -0,0 +1,45 @@ +using System; + +using Xamarin.Android.Tools.Bytecode; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.BytecodeTests { + + [TestFixture] + public class ModuleInfoTests : ClassFileFixture { + + const string JavaType = "module-info"; + + [Test] + public void ClassFile () + { + var c = LoadClassFile (JavaType + ".class"); + new ExpectedTypeDeclaration { + MajorVersion = 0x37, + MinorVersion = 0, + ConstantPoolCount = 13, + AccessFlags = ClassAccessFlags.Module, + FullName = "module-info", + }.Assert (c); + + Assert.AreEqual (2, c.Attributes.Count); + + Assert.AreEqual ("SourceFile", c.Attributes [0].Name); + var sourceFileAttr = c.Attributes [0] as SourceFileAttribute; + Assert.IsTrue (sourceFileAttr != null); + Assert.AreEqual ("module-info.java", sourceFileAttr.FileName); + + Assert.AreEqual ("Module", c.Attributes [1].Name); + var moduleAttr = c.Attributes [1] as ModuleAttribute; + Assert.IsTrue (moduleAttr != null); + Assert.AreEqual ("com.xamarin", moduleAttr.ModuleName); + Assert.AreEqual (null, moduleAttr.ModuleVersion); + Assert.AreEqual (1, moduleAttr.Requires.Count); + Assert.AreEqual ("java.base", moduleAttr.Requires [0].Requires); + Assert.AreEqual (1, moduleAttr.Exports.Count); + Assert.AreEqual ("com/xamarin", moduleAttr.Exports [0].Exports); + } + } +} + diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj index 5c454b91c..232f069d9 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj @@ -49,6 +49,7 @@ + diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.targets b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.targets index 3783e3a2a..2d1437ea8 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.targets +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.targets @@ -3,22 +3,25 @@ - + + <_BuildClassOutputs Include="@(TestJar->'$(IntermediateOutputPath)classes\%(RecursiveDir)%(Filename).class')" /> + <_BuildClassOutputs Include="@(TestJarJdk11->'$(IntermediateOutputPath)classes\%(RecursiveDir)%(Filename).class')" /> <_BuildClassOutputs Include="@(TestJarNoParameters->'$(IntermediateOutputPath)classes\%(RecursiveDir)%(Filename).class')" /> +