Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package plexus-languages for openSUSE:Factory checked in at 2026-11-06 19:58:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/plexus-languages (Old) and /work/SRC/openSUSE:Factory/.plexus-languages.new.3759290 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "plexus-languages" Fri Nov 6 19:58:46 2026 rev:11 rq:1395848 version:1.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/plexus-languages/plexus-languages.changes 2026-09-29 17:50:58.349195354 +0200 +++ /work/SRC/openSUSE:Factory/.plexus-languages.new.3759290/plexus-languages.changes 2026-11-06 20:06:59.721920080 +0100 @@ -1,0 +2,28 @@ +Fri Nov 6 13:02:18 UTC 2026 - Fridrich Strba <[email protected]> + +- Upgrade to upstream version 1.7.0 + * New features and improvements + + Read module descriptors without ASM + + Choose the versioned module descriptor by the target release + * Bug Fixes + + Read the module descriptor of a multi-release output directory + + Read module descriptors newer than ASM supports + * Documentation updates + + Drop the Source Xref menu item + + Stop pinning a version in the README snippet + * Maintenance + + Target 1.7.0 for the next release + + Drop the Source Xref menu item + * Build + + Let the extra matrix entries take the default Maven version + + Use the shared release-drafter workflow + + Build on JDK 26, 27 and 28-ea + + Ignore Mockito 5 in Dependabot + + Draft the version the labels imply, not always a patch + * Dependency updates + + Bump org.ow2.asm:asm from 9.10.1 to 9.11 +- Modified patch: + * plexus-languages-atinject.patch + + rediff + +------------------------------------------------------------------- @@ -65 +93 @@ - + Fix typo module (#125) @Thihup + + Fix typo module Old: ---- plexus-languages-1.6.0.tar.xz New: ---- plexus-languages-1.7.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ plexus-languages.spec ++++++ --- /var/tmp/diff_new_pack.ULOayA/_old 2026-11-06 20:07:09.451327809 +0100 +++ /var/tmp/diff_new_pack.ULOayA/_new 2026-11-06 20:07:09.456328018 +0100 @@ -20,7 +20,7 @@ # binaries are java 8 compatible %define __requires_exclude java-headless Name: plexus-languages -Version: 1.6.0 +Version: 1.7.0 Release: 0 Summary: Plexus Languages License: Apache-2.0 ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.ULOayA/_old 2026-11-06 20:07:09.583333341 +0100 +++ /var/tmp/diff_new_pack.ULOayA/_new 2026-11-06 20:07:09.593333760 +0100 @@ -1,6 +1,6 @@ -mtime: 1790601217 -commit: f4accaa25348904f295a1231a8a4fb5f89ce36df37fcb8180261e981937b6a96 +mtime: 1793970328 +commit: 32d4aa6175503a05b35ec4eb2eb251993ac4890c00f58b59cb7ef808c8ae0a66 url: https://src.opensuse.org/java-packages/plexus-languages -revision: f4accaa25348904f295a1231a8a4fb5f89ce36df37fcb8180261e981937b6a96 +revision: 32d4aa6175503a05b35ec4eb2eb251993ac4890c00f58b59cb7ef808c8ae0a66 projectscmsync: https://src.opensuse.org/java-packages/_ObsPrj ++++++ _service ++++++ --- /var/tmp/diff_new_pack.ULOayA/_old 2026-11-06 20:07:09.648336065 +0100 +++ /var/tmp/diff_new_pack.ULOayA/_new 2026-11-06 20:07:09.655336358 +0100 @@ -2,7 +2,7 @@ <service name="tar_scm" mode="disabled"> <param name="scm">git</param> <param name="url">https://github.com/codehaus-plexus/plexus-languages.git</param> - <param name="revision">plexus-languages-1.6.0</param> + <param name="revision">plexus-languages-1.7.0</param> <param name="match-tag">plexus-languages-*</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">plexus-languages-(.*)</param> ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-11-06 14:05:28.000000000 +0100 @@ -0,0 +1 @@ +.osc ++++++ plexus-java-build.xml ++++++ --- /var/tmp/diff_new_pack.ULOayA/_old 2026-11-06 20:07:09.973349685 +0100 +++ /var/tmp/diff_new_pack.ULOayA/_new 2026-11-06 20:07:09.983350104 +0100 @@ -10,7 +10,7 @@ <property name="project.groupId" value="org.codehaus.plexus"/> <property name="project.artifactId" value="plexus-java"/> - <property name="project.version" value="1.6.0"/> + <property name="project.version" value="1.7.0"/> <property name="compiler.release" value="8"/> <property name="compiler.source" value="1.${compiler.release}"/> ++++++ plexus-languages-1.6.0.tar.xz -> plexus-languages-1.7.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/.github/dependabot.yml new/plexus-languages-1.7.0/.github/dependabot.yml --- old/plexus-languages-1.6.0/.github/dependabot.yml 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/.github/dependabot.yml 2026-11-06 11:37:41.000000000 +0100 @@ -4,6 +4,10 @@ directory: "/" schedule: interval: "daily" + ignore: + # Mockito 5 requires Java 11; the build still runs on JDK 8 + - dependency-name: "org.mockito:*" + versions: [">=5"] - package-ecosystem: "github-actions" directory: "/" schedule: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/.github/release-drafter.yml new/plexus-languages-1.7.0/.github/release-drafter.yml --- old/plexus-languages-1.6.0/.github/release-drafter.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/plexus-languages-1.7.0/.github/release-drafter.yml 2026-11-06 11:37:41.000000000 +0100 @@ -0,0 +1,10 @@ +# Configuration for Release Drafter: https://github.com/release-drafter/release-drafter +# Categories, labels and the version-resolver come from the shared org config. +_extends: .github + +# Tags here are prefixed, so tag-prefix is what lets the drafter parse the +# version out of the last tag. Without it the drafter reads what it can from the +# tag string, and plexus-i18n once drafted 18.0.1 by finding the 18 in its name. +tag-prefix: plexus-languages- +tag-template: plexus-languages-$RESOLVED_VERSION +name-template: $RESOLVED_VERSION diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/.github/workflows/maven.yml new/plexus-languages-1.7.0/.github/workflows/maven.yml --- old/plexus-languages-1.6.0/.github/workflows/maven.yml 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/.github/workflows/maven.yml 2026-11-06 11:37:41.000000000 +0100 @@ -17,12 +17,19 @@ name: GitHub CI -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: name: Build it uses: codehaus-plexus/.github/.github/workflows/maven.yml@master + with: + matrix-include: > + [ + { "os": "ubuntu-latest", "jdk": "26", "distribution": "zulu" }, + { "os": "ubuntu-latest", "jdk": "27", "distribution": "zulu" }, + { "os": "ubuntu-latest", "jdk": "28-ea", "distribution": "temurin" } + ] # deploy: # name: Deploy diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/.github/workflows/release-drafter.yml new/plexus-languages-1.7.0/.github/workflows/release-drafter.yml --- old/plexus-languages-1.6.0/.github/workflows/release-drafter.yml 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/.github/workflows/release-drafter.yml 2026-11-06 11:37:41.000000000 +0100 @@ -3,10 +3,8 @@ push: branches: - master + workflow_dispatch: + jobs: update_release_draft: - runs-on: ubuntu-latest - steps: - - uses: release-drafter/[email protected] - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: codehaus-plexus/.github/.github/workflows/release-drafter.yml@master diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/README.md new/plexus-languages-1.7.0/README.md --- old/plexus-languages-1.6.0/README.md 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/README.md 2026-11-06 11:37:41.000000000 +0100 @@ -26,7 +26,6 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-java</artifactId> - <version>1.5.2</version> </dependency> ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/pom.xml new/plexus-languages-1.7.0/plexus-java/pom.xml --- old/plexus-languages-1.6.0/plexus-java/pom.xml 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/pom.xml 2026-11-06 11:37:41.000000000 +0100 @@ -5,7 +5,7 @@ <parent> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-languages</artifactId> - <version>1.6.0</version> + <version>1.7.0</version> </parent> <artifactId>plexus-java</artifactId> @@ -13,11 +13,6 @@ <dependencies> <dependency> - <groupId>org.ow2.asm</groupId> - <artifactId>asm</artifactId> - <version>9.10.1</version> - </dependency> - <dependency> <groupId>com.github.javaparser</groupId> <artifactId>javaparser-core</artifactId> <version>3.28.2</version> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AbstractBinaryModuleInfoParser.java new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AbstractBinaryModuleInfoParser.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AbstractBinaryModuleInfoParser.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AbstractBinaryModuleInfoParser.java 2026-11-06 11:37:41.000000000 +0100 @@ -39,7 +39,14 @@ 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 { @@ -57,8 +64,7 @@ && "true" .equalsIgnoreCase( manifest.getMainAttributes().getValue("Multi-Release"))) { - int javaVersion = - Integer.parseInt(jdkVersion.asMajor().getValue(1)); + int javaVersion = jdkVersion.getMajorVersion(); for (int version = javaVersion; version >= 9; version--) { String resource = "META-INF/versions/" + version + "/module-info.class"; @@ -82,5 +88,25 @@ 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/<N>}, 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 = jdkVersion.getMajorVersion(); + + 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParser.java new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParser.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParser.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParser.java 1970-01-01 01:00:00.000000000 +0100 @@ -1,107 +0,0 @@ -package org.codehaus.plexus.languages.java.jpms; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ModuleVisitor; -import org.objectweb.asm.Opcodes; - -/** - * Extract information from module with ASM - * - * - * @author Robert Scholte - * @since 1.0.0 - */ -class AsmModuleInfoParser extends AbstractBinaryModuleInfoParser { - @Override - JavaModuleDescriptor parse(InputStream in) throws IOException { - final JavaModuleDescriptorWrapper wrapper = new JavaModuleDescriptorWrapper(); - - ClassReader reader = new ClassReader(in); - reader.accept( - new ClassVisitor(Opcodes.ASM9) { - @Override - public ModuleVisitor visitModule(String name, int arg1, String arg2) { - wrapper.builder = JavaModuleDescriptor.newModule(name); - - return new ModuleVisitor(Opcodes.ASM9) { - @Override - public void visitRequire(String module, int access, String version) { - if ((access & (Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE)) != 0) { - Set<JavaModuleDescriptor.JavaRequires.JavaModifier> modifiers = - new LinkedHashSet<>(); - if ((access & Opcodes.ACC_STATIC_PHASE) != 0) { - modifiers.add(JavaModuleDescriptor.JavaRequires.JavaModifier.STATIC); - } - if ((access & Opcodes.ACC_TRANSITIVE) != 0) { - modifiers.add(JavaModuleDescriptor.JavaRequires.JavaModifier.TRANSITIVE); - } - - wrapper.builder.requires(modifiers, module); - } else { - wrapper.builder.requires(module); - } - } - - @Override - public void visitExport(String pn, int ms, String... targets) { - if (targets == null || targets.length == 0) { - wrapper.builder.exports(pn.replace('/', '.')); - } else { - wrapper.builder.exports( - pn.replace('/', '.'), new HashSet<>(Arrays.asList(targets))); - } - } - - @Override - public void visitUse(String service) { - wrapper.builder.uses(service.replace('/', '.')); - } - - @Override - public void visitProvide(String service, String... providers) { - List<String> renamedProvides = new ArrayList<>(providers.length); - for (String provider : providers) { - renamedProvides.add(provider.replace('/', '.')); - } - wrapper.builder.provides(service.replace('/', '.'), renamedProvides); - } - }; - } - }, - 0); - return wrapper.builder.build(); - } - - private static class JavaModuleDescriptorWrapper { - private JavaModuleDescriptor.Builder builder; - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java 2026-11-06 11:37:41.000000000 +0100 @@ -18,4 +18,4 @@ * specific language governing permissions and limitations * under the License. */ -class BinaryModuleInfoParser extends AsmModuleInfoParser {} +class BinaryModuleInfoParser extends ModuleInfoClassParser {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java 2026-11-06 11:37:41.000000000 +0100 @@ -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 @@ -121,7 +122,8 @@ return resolvePath( request.toPath(request.getPathElement()), filenameExtractor, - getBinaryModuleInfoParser(request.getJdkHome())); + getBinaryModuleInfoParser(request.getJdkHome()), + request.getTargetRelease()); } /** @@ -172,7 +174,8 @@ }; try { - ResolvePathResult resolvedPath = resolvePath(request.toPath(t), nameExtractor, binaryParser); + ResolvePathResult resolvedPath = + resolvePath(request.toPath(t), nameExtractor, binaryParser, request.getTargetRelease()); moduleDescriptor = resolvedPath.getModuleDescriptor(); @@ -276,7 +279,9 @@ /** * If the jdkHome is specified, its version it considered higher than the runtime java version. - * In that case ASM must be used to read the module descriptor + * In that case the version-agnostic {@link ModuleInfoClassParser} must be used to read the module + * descriptor, since the runtime's own {@code java.lang.module.ModuleDescriptor} cannot be trusted + * to read a module-info.class produced by a newer JDK than the one it runs on. * * @param jdkHome * @return @@ -286,7 +291,7 @@ if (jdkHome == null) { binaryParser = new BinaryModuleInfoParser(); } else { - binaryParser = new AsmModuleInfoParser(); + binaryParser = new ModuleInfoClassParser(); } return binaryParser; } @@ -301,7 +306,8 @@ if (descriptorPath.endsWith("module-info.java")) { mainModuleDescriptor = sourceParser.fromSourcePath(descriptorPath); } else if (descriptorPath.endsWith("module-info.class")) { - mainModuleDescriptor = binaryParser.getModuleDescriptor(descriptorPath.getParent()); + mainModuleDescriptor = + binaryParser.getModuleDescriptor(descriptorPath.getParent(), request.getTargetRelease()); } else { throw new IOException("Invalid path to module descriptor: " + descriptorPath); } @@ -312,7 +318,11 @@ } private ResolvePathResult resolvePath( - Path path, ModuleNameExtractor fileModulenameExtractor, ModuleInfoParser binaryParser) throws IOException { + Path path, + ModuleNameExtractor fileModulenameExtractor, + ModuleInfoParser binaryParser, + JavaVersion targetRelease) + throws IOException { ResolvePathResult result = new ResolvePathResult(); JavaModuleDescriptor moduleDescriptor = null; @@ -323,8 +333,10 @@ "'" + path + "' not allowed on the path, only outputDirectories and jars are accepted"); } - if (Files.isRegularFile(path) || Files.exists(path.resolve("module-info.class"))) { - moduleDescriptor = binaryParser.getModuleDescriptor(path); + if (Files.isRegularFile(path) + || Files.exists(path.resolve("module-info.class")) + || AbstractBinaryModuleInfoParser.findVersionedModuleInfo(path, targetRelease) != null) { + moduleDescriptor = binaryParser.getModuleDescriptor(path, targetRelease); } if (moduleDescriptor != null) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParser.java new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParser.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParser.java 1970-01-01 01:00:00.000000000 +0100 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParser.java 2026-11-06 11:37:41.000000000 +0100 @@ -0,0 +1,302 @@ +package org.codehaus.plexus.languages.java.jpms; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Reads the {@code Module} attribute (JVMS 4.7.25) directly out of a {@code module-info.class} file, + * without depending on a bytecode library or on the class-file support of the running JDK. + * + * <p>Only the class file's overall shape (constant pool, then fields, then methods, then attributes) + * needs to be understood to reach the {@code Module} attribute; every other section is skipped using + * its own declared length rather than parsed. The {@code Module} attribute format has been stable + * since it was introduced in Java 9, and this parser never looks at the class file's major/minor + * version, so it reads a {@code module-info.class} produced by any future JDK just as well as one + * from Java 9. + * + * @author Robert Scholte + * @since 1.7.0 + */ +class ModuleInfoClassParser extends AbstractBinaryModuleInfoParser { + + private static final int MAGIC = 0xCAFEBABE; + + // Constant pool tags, JVMS 4.4, Table 4.4-A. + private static final int CONSTANT_UTF8 = 1; + private static final int CONSTANT_INTEGER = 3; + private static final int CONSTANT_FLOAT = 4; + private static final int CONSTANT_LONG = 5; + private static final int CONSTANT_DOUBLE = 6; + private static final int CONSTANT_CLASS = 7; + private static final int CONSTANT_STRING = 8; + private static final int CONSTANT_FIELDREF = 9; + private static final int CONSTANT_METHODREF = 10; + private static final int CONSTANT_INTERFACE_METHODREF = 11; + private static final int CONSTANT_NAME_AND_TYPE = 12; + private static final int CONSTANT_METHOD_HANDLE = 15; + private static final int CONSTANT_METHOD_TYPE = 16; + private static final int CONSTANT_DYNAMIC = 17; + private static final int CONSTANT_INVOKE_DYNAMIC = 18; + private static final int CONSTANT_MODULE = 19; + private static final int CONSTANT_PACKAGE = 20; + + private static final String MODULE_ATTRIBUTE_NAME = "Module"; + + // Module_attribute requires_flags, JVMS 4.7.25. + private static final int ACC_TRANSITIVE = 0x0020; + private static final int ACC_STATIC_PHASE = 0x0040; + + @Override + JavaModuleDescriptor parse(InputStream in) throws IOException { + DataInputStream dis = (in instanceof DataInputStream) ? (DataInputStream) in : new DataInputStream(in); + + int magic = dis.readInt(); + if (magic != MAGIC) { + throw new IOException("Not a class file, bad magic number: 0x" + Integer.toHexString(magic)); + } + + dis.readUnsignedShort(); // minor_version, unused + dis.readUnsignedShort(); // major_version, deliberately unchecked: the Module attribute below + // has the same layout no matter which major version produced the file. + + ConstantPool pool = ConstantPool.read(dis); + + dis.readUnsignedShort(); // access_flags + dis.readUnsignedShort(); // this_class + dis.readUnsignedShort(); // super_class + + int interfacesCount = dis.readUnsignedShort(); + skipFully(dis, 2L * interfacesCount); + + skipMembers(dis); // fields + skipMembers(dis); // methods + + int attributesCount = dis.readUnsignedShort(); + for (int i = 0; i < attributesCount; i++) { + int attributeNameIndex = dis.readUnsignedShort(); + long attributeLength = readU4(dis); + if (MODULE_ATTRIBUTE_NAME.equals(pool.utf8(attributeNameIndex))) { + return readModuleAttribute(dis, pool); + } + skipFully(dis, attributeLength); + } + + throw new IOException("Not a module: no Module attribute found"); + } + + /** + * Skips over a {@code fields} or {@code methods} array: same {@code field_info}/{@code method_info} + * shape, each with its own list of attributes that are skipped by their declared length. + */ + private static void skipMembers(DataInputStream dis) throws IOException { + int count = dis.readUnsignedShort(); + for (int i = 0; i < count; i++) { + skipFully(dis, 6); // access_flags, name_index, descriptor_index + int attributesCount = dis.readUnsignedShort(); + for (int j = 0; j < attributesCount; j++) { + skipFully(dis, 2); // attribute_name_index + long attributeLength = readU4(dis); + skipFully(dis, attributeLength); + } + } + } + + /** + * Reads the body of the {@code Module} attribute (JVMS 4.7.25), mapping it onto a + * {@link JavaModuleDescriptor} the same way {@code java.lang.module.ModuleDescriptor} does. + */ + private static JavaModuleDescriptor readModuleAttribute(DataInputStream dis, ConstantPool pool) throws IOException { + String moduleName = pool.moduleName(dis.readUnsignedShort()); + JavaModuleDescriptor.Builder builder = JavaModuleDescriptor.newModule(moduleName); + + dis.readUnsignedShort(); // module_flags + dis.readUnsignedShort(); // module_version_index + + int requiresCount = dis.readUnsignedShort(); + for (int i = 0; i < requiresCount; i++) { + int requiresIndex = dis.readUnsignedShort(); + int requiresFlags = dis.readUnsignedShort(); + dis.readUnsignedShort(); // requires_version_index + + String requiredModule = pool.moduleName(requiresIndex); + if ((requiresFlags & (ACC_STATIC_PHASE | ACC_TRANSITIVE)) != 0) { + Set<JavaModuleDescriptor.JavaRequires.JavaModifier> modifiers = new LinkedHashSet<>(); + if ((requiresFlags & ACC_STATIC_PHASE) != 0) { + modifiers.add(JavaModuleDescriptor.JavaRequires.JavaModifier.STATIC); + } + if ((requiresFlags & ACC_TRANSITIVE) != 0) { + modifiers.add(JavaModuleDescriptor.JavaRequires.JavaModifier.TRANSITIVE); + } + builder.requires(modifiers, requiredModule); + } else { + builder.requires(requiredModule); + } + } + + int exportsCount = dis.readUnsignedShort(); + for (int i = 0; i < exportsCount; i++) { + int exportsIndex = dis.readUnsignedShort(); + dis.readUnsignedShort(); // exports_flags + int exportsToCount = dis.readUnsignedShort(); + String pkg = pool.packageName(exportsIndex); + if (exportsToCount == 0) { + builder.exports(pkg); + } else { + Set<String> targets = new HashSet<>(); + for (int j = 0; j < exportsToCount; j++) { + targets.add(pool.moduleName(dis.readUnsignedShort())); + } + builder.exports(pkg, targets); + } + } + + int opensCount = dis.readUnsignedShort(); + for (int i = 0; i < opensCount; i++) { + dis.readUnsignedShort(); // opens_index + dis.readUnsignedShort(); // opens_flags + int opensToCount = dis.readUnsignedShort(); + skipFully(dis, 2L * opensToCount); + } + + int usesCount = dis.readUnsignedShort(); + for (int i = 0; i < usesCount; i++) { + builder.uses(pool.className(dis.readUnsignedShort())); + } + + int providesCount = dis.readUnsignedShort(); + for (int i = 0; i < providesCount; i++) { + String service = pool.className(dis.readUnsignedShort()); + int withCount = dis.readUnsignedShort(); + List<String> providers = new ArrayList<>(withCount); + for (int j = 0; j < withCount; j++) { + providers.add(pool.className(dis.readUnsignedShort())); + } + builder.provides(service, providers); + } + + return builder.build(); + } + + private static long readU4(DataInputStream dis) throws IOException { + return dis.readInt() & 0xFFFFFFFFL; + } + + private static void skipFully(DataInputStream dis, long length) throws IOException { + long remaining = length; + while (remaining > 0) { + long skipped = dis.skip(remaining); + if (skipped <= 0) { + // skip() may legitimately return 0 near the end of the stream; fall back to read() + if (dis.read() < 0) { + throw new IOException("Unexpected end of class file"); + } + skipped = 1; + } + remaining -= skipped; + } + } + + /** + * The subset of the constant pool this parser needs: the raw UTF-8 entries, and, for the + * Class/Module/Package entries, which UTF-8 entry holds their name. All three of those tags + * have the same one-field shape (JVMS 4.4.1, 4.4.8, 4.4.9): {@code u1 tag; u2 name_index;}. + */ + private static final class ConstantPool { + private final String[] utf8; + private final int[] classLikeNameIndex; + + private ConstantPool(String[] utf8, int[] classLikeNameIndex) { + this.utf8 = utf8; + this.classLikeNameIndex = classLikeNameIndex; + } + + static ConstantPool read(DataInputStream dis) throws IOException { + int constantPoolCount = dis.readUnsignedShort(); + String[] utf8 = new String[constantPoolCount]; + int[] classLikeNameIndex = new int[constantPoolCount]; + + for (int i = 1; i < constantPoolCount; i++) { + int tag = dis.readUnsignedByte(); + switch (tag) { + case CONSTANT_UTF8: + utf8[i] = dis.readUTF(); + break; + case CONSTANT_CLASS: + case CONSTANT_MODULE: + case CONSTANT_PACKAGE: + classLikeNameIndex[i] = dis.readUnsignedShort(); + break; + case CONSTANT_STRING: + skipFully(dis, 2); + break; + case CONSTANT_INTEGER: + case CONSTANT_FLOAT: + case CONSTANT_FIELDREF: + case CONSTANT_METHODREF: + case CONSTANT_INTERFACE_METHODREF: + case CONSTANT_NAME_AND_TYPE: + case CONSTANT_DYNAMIC: + case CONSTANT_INVOKE_DYNAMIC: + skipFully(dis, 4); + break; + case CONSTANT_LONG: + case CONSTANT_DOUBLE: + skipFully(dis, 8); + // long/double take up two consecutive constant pool entries; the second is unused + i++; + break; + case CONSTANT_METHOD_HANDLE: + skipFully(dis, 3); + break; + case CONSTANT_METHOD_TYPE: + skipFully(dis, 2); + break; + default: + throw new IOException("Unsupported constant pool tag " + tag + " at index " + i); + } + } + return new ConstantPool(utf8, classLikeNameIndex); + } + + String utf8(int index) { + return utf8[index]; + } + + String moduleName(int index) { + return utf8[classLikeNameIndex[index]]; + } + + String packageName(int index) { + return utf8[classLikeNameIndex[index]].replace('/', '.'); + } + + String className(int index) { + return utf8[classLikeNameIndex[index]].replace('/', '.'); + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ResolvePathRequest.java new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ResolvePathRequest.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ResolvePathRequest.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ResolvePathRequest.java 2026-11-06 11:37:41.000000000 +0100 @@ -22,6 +22,9 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Objects; + +import org.codehaus.plexus.languages.java.version.JavaVersion; /** * @@ -33,6 +36,8 @@ private T path; + private JavaVersion targetRelease = JavaVersion.JAVA_SPECIFICATION_VERSION; + private ResolvePathRequest() {} public static ResolvePathRequest<File> ofFile(File file) { @@ -88,4 +93,29 @@ public Path getJdkHome() { return jdkHome; } + + /** + * The Java release the module descriptor must be resolved for - the {@code javac --release} value, or + * {@code -target} when {@code --release} is unset. Accepts {@code "1.8"}-style values as well as bare + * major versions. This picks the applicable versioned {@code module-info.class} of a multi-release jar or + * output directory, i.e. the highest {@code META-INF/versions/<N>} whose {@code N} does not exceed this + * release. Defaults to the version of the running JDK, so callers that compile for a different release + * should set this explicitly. + * + * @param targetRelease the target release, never {@code null} + * @return this request + * @since 1.7.0 + */ + public ResolvePathRequest<T> setTargetRelease(JavaVersion targetRelease) { + this.targetRelease = Objects.requireNonNull(targetRelease, "targetRelease"); + return this; + } + + /** + * @return the Java release the module descriptor is resolved for, defaults to the running JDK's version + * @since 1.7.0 + */ + public JavaVersion getTargetRelease() { + return targetRelease; + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ResolvePathsRequest.java new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ResolvePathsRequest.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ResolvePathsRequest.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ResolvePathsRequest.java 2026-11-06 11:37:41.000000000 +0100 @@ -25,6 +25,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Objects; + +import org.codehaus.plexus.languages.java.version.JavaVersion; /** * Contains all information required to analyze the project @@ -47,6 +50,8 @@ private boolean includeStatic; + private JavaVersion targetRelease = JavaVersion.JAVA_SPECIFICATION_VERSION; + private ResolvePathsRequest() {} /** @@ -226,4 +231,29 @@ this.includeStatic = includeStatic; return this; } + + /** + * The Java release the module descriptors must be resolved for - the {@code javac --release} value, or + * {@code -target} when {@code --release} is unset. Accepts {@code "1.8"}-style values as well as bare + * major versions. This picks the applicable versioned {@code module-info.class} of a multi-release jar or + * output directory, i.e. the highest {@code META-INF/versions/<N>} whose {@code N} does not exceed this + * release. Defaults to the version of the running JDK, so callers that compile for a different release + * should set this explicitly. + * + * @param targetRelease the target release, never {@code null} + * @return this request + * @since 1.7.0 + */ + public ResolvePathsRequest<T> setTargetRelease(JavaVersion targetRelease) { + this.targetRelease = Objects.requireNonNull(targetRelease, "targetRelease"); + return this; + } + + /** + * @return the Java release module descriptors are resolved for, defaults to the running JDK's version + * @since 1.7.0 + */ + public JavaVersion getTargetRelease() { + return targetRelease; + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/version/JavaVersion.java new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/version/JavaVersion.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/version/JavaVersion.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java/org/codehaus/plexus/languages/java/version/JavaVersion.java 2026-11-06 11:37:41.000000000 +0100 @@ -219,6 +219,23 @@ return value.toString(); } + /** + * The major version as an integer, parsed from the leading digits of {@link #asMajor()}'s value, so a + * pre-release suffix such as {@code "-ea"} does not prevent parsing, e.g. both {@code "21"} and + * {@code "21-ea"} return {@code 21}, and {@code "1.8"} returns {@code 8}. + * + * @return the major version number + * @throws NumberFormatException if the version does not start with a digit + * @since 1.7.0 + */ + public int getMajorVersion() { + Matcher matcher = startingDigits.matcher(asMajor().getValue(1)); + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)); + } + throw new NumberFormatException("Cannot parse a major version from '" + rawVersion + "'"); + } + @Override public String toString() { return rawVersion; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/main/java9/module-info.java new/plexus-languages-1.7.0/plexus-java/src/main/java9/module-info.java --- old/plexus-languages-1.6.0/plexus-java/src/main/java9/module-info.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/main/java9/module-info.java 2026-11-06 11:37:41.000000000 +0100 @@ -18,7 +18,6 @@ */ module org.codehaus.plexus.languages.java { requires com.github.javaparser.core; - requires org.objectweb.asm; exports org.codehaus.plexus.languages.java.jpms; exports org.codehaus.plexus.languages.java.version; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/site/site.xml new/plexus-languages-1.7.0/plexus-java/src/site/site.xml --- old/plexus-languages-1.6.0/plexus-java/src/site/site.xml 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/site/site.xml 2026-11-06 11:37:41.000000000 +0100 @@ -27,7 +27,6 @@ <item name="Usage" href="usage.html"/> <item name="LocationManager" href="locationmanager.html"/> <item name="JavaDocs" href="apidocs/index.html"/> - <item name="Source Xref" href="xref/index.html"/> </menu> <menu ref="parent"/> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParserTest.java new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParserTest.java --- old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParserTest.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParserTest.java 2026-11-06 11:37:41.000000000 +0100 @@ -94,6 +94,16 @@ } @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 = parser.getModuleDescriptor(Paths.get("src/test/test-data/dir.descriptor/out")); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTargetReleaseTest.java new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTargetReleaseTest.java --- old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTargetReleaseTest.java 1970-01-01 01:00:00.000000000 +0100 +++ new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTargetReleaseTest.java 2026-11-06 11:37:41.000000000 +0100 @@ -0,0 +1,103 @@ +package org.codehaus.plexus.languages.java.jpms; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; + +import org.codehaus.plexus.languages.java.version.JavaVersion; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * End-to-end coverage of {@link ResolvePathsRequest#setTargetRelease(JavaVersion)}, using the real + * {@link BinaryModuleInfoParser} against a directory that has a versioned module descriptor for both Java 11 + * and Java 17. Runs on every JDK, including Java 8, since the whole point of the target release setting is to + * pick the applicable versioned descriptor without relying on the version of the JDK that runs the build. + */ +class LocationManagerTargetReleaseTest { + private final LocationManager locationManager = new LocationManager(); + + private final Path dir = Paths.get("src/test/test-data/dir.mr.target.release/out"); + + @Test + void targetReleaseSelectsItsOwnVersionedDescriptor() throws Exception { + ResolvePathsRequest<Path> request = + ResolvePathsRequest.ofPaths(Collections.singletonList(dir)).setTargetRelease(JavaVersion.parse("11")); + + ResolvePathsResult<Path> result = locationManager.resolvePaths(request); + + assertThat(result.getPathElements().get(dir).name()).isEqualTo("dir.mr.target.release.v11"); + } + + @Test + void targetReleaseWorksIndependentlyOfTheRunningJdk() throws Exception { + // On a Java 8 runtime compiling through a Java 9+ toolchain, the running JDK's version must not be used + // to pick the versioned descriptor - this is the scenario from #237 (Maven 3.9 on JDK 8, --release 17). + ResolvePathsRequest<Path> request = + ResolvePathsRequest.ofPaths(Collections.singletonList(dir)).setTargetRelease(JavaVersion.parse("17")); + + ResolvePathsResult<Path> result = locationManager.resolvePaths(request); + + assertThat(result.getPathElements().get(dir).name()).isEqualTo("dir.mr.target.release.v17"); + } + + @Test + void targetReleaseBelowEveryVersionedDescriptorSelectsNone() throws Exception { + ResolvePathsRequest<Path> request = + ResolvePathsRequest.ofPaths(Collections.singletonList(dir)).setTargetRelease(JavaVersion.parse("9")); + + ResolvePathsResult<Path> result = locationManager.resolvePaths(request); + + assertThat(result.getPathElements().get(dir)).isNull(); + } + + @Test + void preReleaseTargetRelease() throws Exception { + ResolvePathsRequest<Path> request = ResolvePathsRequest.ofPaths(Collections.singletonList(dir)) + .setTargetRelease(JavaVersion.parse("11-ea")); + + ResolvePathsResult<Path> result = locationManager.resolvePaths(request); + + assertThat(result.getPathExceptions()).isEmpty(); + assertThat(result.getPathElements().get(dir).name()).isEqualTo("dir.mr.target.release.v11"); + } + + @Test + void singlePathTargetRelease() throws Exception { + ResolvePathRequest<Path> request = ResolvePathRequest.ofPath(dir).setTargetRelease(JavaVersion.parse("11")); + + ResolvePathResult result = locationManager.resolvePath(request); + + assertThat(result.getModuleDescriptor().name()).isEqualTo("dir.mr.target.release.v11"); + } + + @Test + void nullTargetReleaseIsRejected() { + assertThatThrownBy(() -> ResolvePathsRequest.ofPaths(Collections.singletonList(dir)) + .setTargetRelease(null)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> ResolvePathRequest.ofPath(dir).setTargetRelease(null)) + .isInstanceOf(NullPointerException.class); + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTest.java new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTest.java --- old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTest.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/LocationManagerTest.java 2026-11-06 11:37:41.000000000 +0100 @@ -27,12 +27,17 @@ import java.util.HashSet; import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaRequires.JavaModifier; +import org.codehaus.plexus.languages.java.version.JavaVersion; 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; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class LocationManagerTest { @@ -45,7 +50,7 @@ final Path mockModuleInfoJava = Paths.get("src/test/test-data/mock/module-info.java"); @BeforeEach - void onSetup() { + void onSetup() throws Exception { asmParser = mock(BinaryModuleInfoParser.class); sourceParser = mock(SourceModuleInfoParser.class); locationManager = new LocationManager(sourceParser) { @@ -54,6 +59,13 @@ return asmParser; } }; + + // LocationManager always calls the 2-arg overload now; delegate it to the 1-arg overload so every + // existing when(asmParser.getModuleDescriptor(x)) stub below keeps working regardless of which + // JavaVersion production code passes. Tests that care about the JavaVersion argument itself stub the + // 2-arg overload explicitly, which takes precedence over this catch-all. + when(asmParser.getModuleDescriptor(any(Path.class), any(JavaVersion.class))) + .thenAnswer(invocation -> asmParser.getModuleDescriptor(invocation.getArgument(0, Path.class))); } @Test @@ -107,6 +119,28 @@ } @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<Path> request = + ResolvePathsRequest.ofPaths(Collections.singletonList(abc)).setMainModuleDescriptor(mockModuleInfoJava); + + when(asmParser.getModuleDescriptor(abc)) + .thenReturn(JavaModuleDescriptor.newModule("dir.mr.descriptor").build()); + + ResolvePathsResult<Path> 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"); JavaModuleDescriptor descriptor = JavaModuleDescriptor.newModule("base") @@ -291,6 +325,38 @@ } @Test + void unsetTargetReleaseDefaultsToRunningJdk() throws Exception { + Path p = Paths.get("src/test/test-data/mock/jar0.jar"); + ResolvePathsRequest<Path> request = ResolvePathsRequest.ofPaths(Collections.singletonList(p)); + + assertThat(request.getTargetRelease()).isEqualTo(JavaVersion.JAVA_SPECIFICATION_VERSION); + + doReturn(JavaModuleDescriptor.newModule("org.objectweb.asm").build()) + .when(asmParser) + .getModuleDescriptor(p, JavaVersion.JAVA_SPECIFICATION_VERSION); + + locationManager.resolvePaths(request); + + verify(asmParser).getModuleDescriptor(p, JavaVersion.JAVA_SPECIFICATION_VERSION); + } + + @Test + void targetReleaseIsPassedToBinaryParser() throws Exception { + Path p = Paths.get("src/test/test-data/mock/jar0.jar"); + JavaVersion targetRelease = JavaVersion.parse("11"); + ResolvePathsRequest<Path> request = + ResolvePathsRequest.ofPaths(Collections.singletonList(p)).setTargetRelease(targetRelease); + + doReturn(JavaModuleDescriptor.newModule("org.objectweb.asm").build()) + .when(asmParser) + .getModuleDescriptor(p, targetRelease); + + locationManager.resolvePaths(request); + + verify(asmParser).getModuleDescriptor(p, targetRelease); + } + + @Test void noMatchingProviders() throws Exception { Path abc = Paths.get("src/test/test-data/mock/module-info.java"); // some file called module-info.java Path def = Paths.get("src/test/test-data/mock/jar0.jar"); // any existing file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParserTest.java new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParserTest.java --- old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParserTest.java 1970-01-01 01:00:00.000000000 +0100 +++ new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParserTest.java 2026-11-06 11:37:41.000000000 +0100 @@ -0,0 +1,103 @@ +package org.codehaus.plexus.languages.java.jpms; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.ByteArrayInputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaRequires; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ModuleInfoClassParserTest { + private final ModuleInfoClassParser parser = new ModuleInfoClassParser(); + + /** + * This parser never looks at the class file's major/minor version, so a module-info.class compiled + * by a JDK newer than any released today (e.g. major version 72 for JDK 28, see + * codehaus-plexus/plexus-languages#165, or an arbitrarily higher one) must still parse, with the same + * descriptor as the unpatched file (see {@code BinaryModuleInfoParserTest#requires()}). + */ + @ParameterizedTest + @ValueSource(ints = {72, 99}) + void parsesModuleInfoWithMajorVersionNewerThanAnyKnownJdk(int majorVersion) throws Exception { + byte[] classBytes = + Files.readAllBytes(Paths.get("src/test/test-data/dir.descriptor.requires/out/module-info.class")); + + // bytes 6-7 are the big-endian major version + classBytes[6] = (byte) (majorVersion >>> 8); + classBytes[7] = (byte) majorVersion; + + JavaModuleDescriptor descriptor; + try (ByteArrayInputStream is = new ByteArrayInputStream(classBytes)) { + descriptor = parser.parse(is); + } + + assertNotNull(descriptor); + assertThat(descriptor.requires()).hasSize(5); + + Set<JavaRequires> expectedRequires = JavaModuleDescriptor.newAutomaticModule("_") + .requires("java.base") + .requires("mod_r") + .requires(Collections.singleton(JavaRequires.JavaModifier.STATIC), "mod_r_s") + .requires(Collections.singleton(JavaRequires.JavaModifier.TRANSITIVE), "mod_r_t") + .requires( + new HashSet<>( + Arrays.asList(JavaRequires.JavaModifier.STATIC, JavaRequires.JavaModifier.TRANSITIVE)), + "mod_r_s_t") + .build() + .requires(); + + assertEquals(expectedRequires, descriptor.requires()); + } + + @Test + void rejectsBadMagic() throws Exception { + byte[] classBytes = + Files.readAllBytes(Paths.get("src/test/test-data/dir.descriptor.requires/out/module-info.class")); + classBytes[0] = 0x00; + + try (ByteArrayInputStream is = new ByteArrayInputStream(classBytes)) { + assertThrows(java.io.IOException.class, () -> parser.parse(is)); + } + } + + @Test + void rejectsClassFileWithoutModuleAttribute() throws Exception { + // an ordinary compiled class, i.e. one without a Module attribute, must be rejected rather than + // silently producing a bogus descriptor + byte[] classBytes = Files.readAllBytes(Paths.get("src/test/test-data/classfile.version/helloworld-17.class")); + + try (ByteArrayInputStream is = new ByteArrayInputStream(classBytes)) { + assertThrows(java.io.IOException.class, () -> parser.parse(is)); + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/version/JavaVersionTest.java new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/version/JavaVersionTest.java --- old/plexus-languages-1.6.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/version/JavaVersionTest.java 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/plexus-java/src/test/java/org/codehaus/plexus/languages/java/version/JavaVersionTest.java 2026-11-06 11:37:41.000000000 +0100 @@ -166,4 +166,12 @@ assertThat(JavaVersion.parse("3.2.1").getValue(2)).isEqualTo("3.2"); assertThat(JavaVersion.parse("3.2.1").getValue(3)).isEqualTo("3.2.1"); } + + @Test + void getMajorVersion() { + assertEquals(8, JavaVersion.parse("1.8").getMajorVersion()); + assertEquals(11, JavaVersion.parse("11.0.2").getMajorVersion()); + assertEquals(21, JavaVersion.parse("21-ea").getMajorVersion()); + assertEquals(25, JavaVersion.parse("25").getMajorVersion()); + } } Binary files old/plexus-languages-1.6.0/plexus-java/src/test/test-data/dir.mr.descriptor/out/META-INF/versions/9/module-info.class and new/plexus-languages-1.7.0/plexus-java/src/test/test-data/dir.mr.descriptor/out/META-INF/versions/9/module-info.class differ Binary files old/plexus-languages-1.6.0/plexus-java/src/test/test-data/dir.mr.target.release/out/META-INF/versions/11/module-info.class and new/plexus-languages-1.7.0/plexus-java/src/test/test-data/dir.mr.target.release/out/META-INF/versions/11/module-info.class differ Binary files old/plexus-languages-1.6.0/plexus-java/src/test/test-data/dir.mr.target.release/out/META-INF/versions/17/module-info.class and new/plexus-languages-1.7.0/plexus-java/src/test/test-data/dir.mr.target.release/out/META-INF/versions/17/module-info.class differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/plexus-languages-1.6.0/pom.xml new/plexus-languages-1.7.0/pom.xml --- old/plexus-languages-1.6.0/pom.xml 2026-08-19 10:11:21.000000000 +0200 +++ new/plexus-languages-1.7.0/pom.xml 2026-11-06 11:37:41.000000000 +0100 @@ -9,7 +9,7 @@ </parent> <artifactId>plexus-languages</artifactId> - <version>1.6.0</version> + <version>1.7.0</version> <packaging>pom</packaging> <name>Plexus Languages</name> @@ -22,7 +22,7 @@ <scm> <connection>scm:git:https://github.com/codehaus-plexus/plexus-languages.git</connection> <developerConnection>scm:git:https://github.com/codehaus-plexus/plexus-languages.git</developerConnection> - <tag>plexus-languages-1.6.0</tag> + <tag>plexus-languages-1.7.0</tag> <url>https://github.com/codehaus-plexus/plexus-languages/tree/plexus-languages</url> </scm> <issueManagement> @@ -42,7 +42,7 @@ <properties> <scm.url>scm:git:https://github.com/codehaus-plexus/plexus-languages.git</scm.url> - <project.build.outputTimestamp>2026-08-19T08:11:15Z</project.build.outputTimestamp> + <project.build.outputTimestamp>2026-11-06T10:37:26Z</project.build.outputTimestamp> </properties> <build> @@ -120,5 +120,16 @@ </plugins> </build> </profile> + <profile> + <!-- palantir-java-format cannot run on JDK 27+: https://github.com/palantir/palantir-java-format/issues/1785 --> + <id>jdk27-skip-spotless</id> + <activation> + <jdk>[27,)</jdk> + </activation> + <properties> + <spotless.check.skip>true</spotless.check.skip> + <spotless.apply.skip>true</spotless.apply.skip> + </properties> + </profile> </profiles> </project> ++++++ plexus-languages-atinject.patch ++++++ --- /var/tmp/diff_new_pack.ULOayA/_old 2026-11-06 20:07:10.252361378 +0100 +++ /var/tmp/diff_new_pack.ULOayA/_new 2026-11-06 20:07:10.256361545 +0100 @@ -1,9 +1,11 @@ +diff --git a/plexus-java/src/main/java9/module-info.java b/plexus-java/src/main/java9/module-info.java +index 1a68b2a..c8c1229 100644 --- a/plexus-java/src/main/java9/module-info.java +++ b/plexus-java/src/main/java9/module-info.java -@@ -19,6 +19,7 @@ +@@ -18,6 +18,7 @@ + */ module org.codehaus.plexus.languages.java { requires com.github.javaparser.core; - requires org.objectweb.asm; + requires java.inject; exports org.codehaus.plexus.languages.java.jpms;
