diff --git a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AbstractBinaryModuleInfoParser.java b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AbstractBinaryModuleInfoParser.java index 0abd925..b39bc2d 100644 --- a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AbstractBinaryModuleInfoParser.java +++ b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AbstractBinaryModuleInfoParser.java @@ -39,7 +39,14 @@ public JavaModuleDescriptor getModuleDescriptor(Path modulePath) throws IOExcept public JavaModuleDescriptor getModuleDescriptor(Path modulePath, JavaVersion jdkVersion) throws IOException { JavaModuleDescriptor descriptor; if (Files.isDirectory(modulePath)) { - try (InputStream in = Files.newInputStream(modulePath.resolve("module-info.class"))) { + Path moduleInfo = modulePath.resolve("module-info.class"); + if (!Files.exists(moduleInfo)) { + Path versionedModuleInfo = findVersionedModuleInfo(modulePath, jdkVersion); + if (versionedModuleInfo != null) { + moduleInfo = versionedModuleInfo; + } + } + try (InputStream in = Files.newInputStream(moduleInfo)) { descriptor = parse(in); } } else { @@ -82,5 +89,25 @@ public JavaModuleDescriptor getModuleDescriptor(Path modulePath, JavaVersion jdk return descriptor; } + /** + * Finds the module descriptor of a multi-release output directory, which has no {@code module-info.class} at its + * root but under {@code META-INF/versions/}, the way a multi-release jar does. + * + * @param directory the output directory + * @param jdkVersion the highest version to consider + * @return the path of the highest applicable versioned descriptor, or {@code null} if there is none + */ + static Path findVersionedModuleInfo(Path directory, JavaVersion jdkVersion) { + int javaVersion = Integer.parseInt(jdkVersion.asMajor().getValue(1)); + + for (int version = javaVersion; version >= 9; version--) { + Path moduleInfo = directory.resolve("META-INF/versions/" + version + "/module-info.class"); + if (Files.exists(moduleInfo)) { + return moduleInfo; + } + } + return null; + } + abstract JavaModuleDescriptor parse(InputStream in) throws IOException; } diff --git a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java index 2c866cf..632a9e3 100644 --- a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java +++ b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java @@ -36,6 +36,7 @@ import java.util.Set; import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaProvides; +import org.codehaus.plexus.languages.java.version.JavaVersion; /** * Maps artifacts to modules and analyzes the type of required modules @@ -323,7 +324,10 @@ private ResolvePathResult resolvePath( "'" + path + "' not allowed on the path, only outputDirectories and jars are accepted"); } - if (Files.isRegularFile(path) || Files.exists(path.resolve("module-info.class"))) { + if (Files.isRegularFile(path) + || Files.exists(path.resolve("module-info.class")) + || AbstractBinaryModuleInfoParser.findVersionedModuleInfo(path, JavaVersion.JAVA_SPECIFICATION_VERSION) + != null) { moduleDescriptor = binaryParser.getModuleDescriptor(path); } diff --git a/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParserTest.java b/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParserTest.java index bba54b7..eca787a 100644 --- a/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParserTest.java +++ b/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParserTest.java @@ -93,6 +93,16 @@ void classicJar() throws Exception { assertNull(descriptor); } + @Test + void multiReleaseOutputDirectoryDescriptor() throws Exception { + JavaModuleDescriptor descriptor = parser.getModuleDescriptor( + Paths.get("src/test/test-data/dir.mr.descriptor/out"), JavaVersion.parse("17")); + + assertNotNull(descriptor); + assertEquals("org.codehaus.plexus.languages.java.demo", descriptor.name()); + assertFalse(descriptor.isAutomatic()); + } + @Test void outputDirectoryDescriptor() throws Exception { JavaModuleDescriptor descriptor = diff --git a/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTest.java b/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTest.java index 0fe8f29..69df2f1 100644 --- a/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTest.java +++ b/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTest.java @@ -29,6 +29,8 @@ import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaRequires.JavaModifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -106,6 +108,28 @@ void manifestWithReflectRequires() throws Exception { assertThat(result.getPathExceptions()).isEmpty(); } + @Test + @DisabledOnJre(value = JRE.JAVA_8, disabledReason = "Versioned descriptors are only read on Java 9+") + void multiReleaseDirDescriptor() throws Exception { + Path abc = Paths.get("src/test/test-data/dir.mr.descriptor/out"); + JavaModuleDescriptor descriptor = JavaModuleDescriptor.newModule("base") + .requires("dir.mr.descriptor") + .build(); + when(sourceParser.fromSourcePath(any(Path.class))).thenReturn(descriptor); + ResolvePathsRequest request = + ResolvePathsRequest.ofPaths(Collections.singletonList(abc)).setMainModuleDescriptor(mockModuleInfoJava); + + when(asmParser.getModuleDescriptor(abc)) + .thenReturn(JavaModuleDescriptor.newModule("dir.mr.descriptor").build()); + + ResolvePathsResult result = locationManager.resolvePaths(request); + + assertThat(result.getModulepathElements()).hasSize(1); + assertThat(result.getModulepathElements().get(abc)).isEqualTo(ModuleNameSource.MODULEDESCRIPTOR); + assertThat(result.getClasspathElements()).isEmpty(); + assertThat(result.getPathExceptions()).isEmpty(); + } + @Test void dirDescriptorWithReflectRequires() throws Exception { Path abc = Paths.get("src/test/test-data/dir.descriptor/out"); diff --git a/plexus-java/src/test/test-data/dir.mr.descriptor/out/META-INF/versions/9/module-info.class b/plexus-java/src/test/test-data/dir.mr.descriptor/out/META-INF/versions/9/module-info.class new file mode 100644 index 0000000..653dc58 Binary files /dev/null and b/plexus-java/src/test/test-data/dir.mr.descriptor/out/META-INF/versions/9/module-info.class differ