From 8420d58d2527fea1ffb32f3b9e191dd8a22e8ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sol=C3=B3rzano?= Date: Sat, 4 Nov 2023 00:59:14 +0100 Subject: [PATCH] [MCOMPILER-542] Clean JDK patch version in module-info.class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jorge Solórzano --- src/it/MCOMPILER-542/invoker.properties | 2 +- src/it/MCOMPILER-542/pom.xml | 108 ++++++++++-------- .../src/main/java/module-info.java | 6 +- .../src/main/java/org/maven/test/Main.java | 3 - src/it/MCOMPILER-542/verify.groovy | 91 ++++++++++----- .../plugin/compiler/AbstractCompilerMojo.java | 36 ++++++ .../maven/plugin/compiler/CompilerMojo.java | 17 +-- .../compiler/ModuleInfoTransformer.java | 67 +++++++++++ 8 files changed, 233 insertions(+), 97 deletions(-) create mode 100644 src/main/java/org/apache/maven/plugin/compiler/ModuleInfoTransformer.java diff --git a/src/it/MCOMPILER-542/invoker.properties b/src/it/MCOMPILER-542/invoker.properties index 95117185..1c24cc31 100644 --- a/src/it/MCOMPILER-542/invoker.properties +++ b/src/it/MCOMPILER-542/invoker.properties @@ -15,4 +15,4 @@ # specific language governing permissions and limitations # under the License. -invoker.java.version = 9+ +invoker.java.version = 11+ diff --git a/src/it/MCOMPILER-542/pom.xml b/src/it/MCOMPILER-542/pom.xml index d8cbe3ef..b294fed3 100644 --- a/src/it/MCOMPILER-542/pom.xml +++ b/src/it/MCOMPILER-542/pom.xml @@ -1,50 +1,58 @@ - - - - - 4.0.0 - - org.apache.maven.plugins.compiler.it - MCOMPILER-542 - 1.0-SNAPSHOT - ${java.specification.version} - - - UTF-8 - 2023-08-14T15:12:12Z - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - @project.version@ - - ${java.specification.version} - - - - - - + + + + + 4.0.0 + + org.apache.maven.plugins.compiler.it + MCOMPILER-542 + 1.0-SNAPSHOT + ${java.specification.version} + + + UTF-8 + 2023-08-14T15:12:12Z + + + + + org.slf4j + slf4j-jdk-platform-logging + 2.0.9 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + @project.version@ + + ${java.specification.version} + + + + + + diff --git a/src/it/MCOMPILER-542/src/main/java/module-info.java b/src/it/MCOMPILER-542/src/main/java/module-info.java index cfa968a8..ad85f6fe 100644 --- a/src/it/MCOMPILER-542/src/main/java/module-info.java +++ b/src/it/MCOMPILER-542/src/main/java/module-info.java @@ -16,4 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -module app {} +module app { + requires java.logging; + requires jdk.zipfs; + requires org.slf4j.jdk.platform.logging; +} diff --git a/src/it/MCOMPILER-542/src/main/java/org/maven/test/Main.java b/src/it/MCOMPILER-542/src/main/java/org/maven/test/Main.java index 8d175576..11bc3de4 100644 --- a/src/it/MCOMPILER-542/src/main/java/org/maven/test/Main.java +++ b/src/it/MCOMPILER-542/src/main/java/org/maven/test/Main.java @@ -20,9 +20,6 @@ public class Main { - /** - * @param args - */ public static void main(String[] args) { System.out.println("Hello World!"); } diff --git a/src/it/MCOMPILER-542/verify.groovy b/src/it/MCOMPILER-542/verify.groovy index 0ac0a7d2..30217ce6 100644 --- a/src/it/MCOMPILER-542/verify.groovy +++ b/src/it/MCOMPILER-542/verify.groovy @@ -1,30 +1,61 @@ -/* - * 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. - */ - -def proc = 'javap -v target/classes/module-info.class'.execute(null,basedir) -def sout = new StringBuilder(), serr = new StringBuilder() -proc.consumeProcessOutput(sout, serr) -proc.waitForOrKill(1000) -def out = sout.toString() -println "javap -v target/classes/module-info.class>\n$out\nerr> $serr" - -def module = out.substring(out.indexOf('Module:')) -def javaVersion = System.getProperty('java.version') -assert module.contains('// "java.base" ACC_MANDATED') -assert !module.contains(javaVersion) +/* + * 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. + */ + +// Check if the javap tool is available +def javapTool = java.util.spi.ToolProvider.findFirst("javap") +assert javapTool.isPresent() : "javap tool not found. Make sure you have the JDK installed." + +def moduleDescriptor = new File(basedir, "target/classes/module-info.class") +// Create a list of arguments to pass to the javap tool +String[] args = ["-v", moduleDescriptor] + +def swout = new StringWriter(), swerr = new StringWriter() +// Execute the javap tool with args +def result = javapTool.get().run(new PrintWriter(swout), new PrintWriter(swerr), args) +println swerr.toString().isEmpty() ? "javap output:\n$swout" : "javap error:\n$swerr" +assert (result == 0) : "javap run failed" + +// Assertions of module content +def out = swout.toString() +assert out.contains('// "java.base" ACC_MANDATED') : "module not found in module-info.class" +assert out.contains('// "java.logging"') : "module not found in module-info.class" +assert out.contains('// "jdk.zipfs"') : "module not found in module-info.class" +assert out.contains('// "org.slf4j.jdk.platform.logging"') : "module not found in module-info.class" +assert out.contains('// 2.0.9') : "version of org.slf4j.jdk.platform.logging module not found" + +// Validation that the module-info should not contain the full java version but the spec version. +def javaVersion = System.getProperty('java.version') +def javaSpecVersion = System.getProperty('java.specification.version') +if (javaVersion != javaSpecVersion) { // handle the case when is the first release + assert !out.contains('// ' + javaVersion) : "full java version found in module descriptor" +} +assert out.contains('// ' + javaSpecVersion) : "java specification version not found in module descriptor" + +// Additional validation that the checksum is always the same +def checksumMap = [ + '21': 'SHA-256 checksum ccc6515c8fc1bf4e675e205b2a5200d02545b06014b304c292eeddc68cffee8d', + '17': 'SHA-256 checksum 102f24c71aff97210d66ef791b7d56f8a25ff8692d2c97b21682bc7170aaca9c', + '11': 'MD5 checksum 5779cc6044dcba6ae4060e5a2f8a32c8' +] + +def expectedChecksum = checksumMap[javaSpecVersion] +if (expectedChecksum) { + println "Java version: $javaVersion" + assert out.contains(expectedChecksum) : "checksum doesn't match expected output" +} diff --git a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java index 89754ee5..0af21429 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java +++ b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java @@ -644,6 +644,15 @@ protected final MavenProject getProject() { return project; } + protected final Optional getModuleDeclaration(final Set sourceFiles) { + for (File sourceFile : sourceFiles) { + if ("module-info.java".equals(sourceFile.getName())) { + return Optional.of(sourceFile.toPath()); + } + } + return Optional.empty(); + } + private boolean targetOrReleaseSet; @Override @@ -1177,6 +1186,8 @@ public void execute() throws MojoExecutionException, CompilationFailureException } } + patchJdkModuleVersion(compilerResult, sources); + if (useIncrementalCompilation) { if (incrementalBuildHelperRequest.getOutputDirectory().exists()) { getLog().debug("incrementalBuildHelper#afterRebuildExecution"); @@ -1798,4 +1809,29 @@ public void setRelease(String release) { final String getImplicit() { return implicit; } + + /** + * Patch module-info.class to set the java release version for java/jdk modules. + * + * @param compilerResult should succeed. + * @param sources the list of the source files to check for the "module-info.java" + * + * @see MCOMPILER-542 + * @see JDK-8318913 + */ + private void patchJdkModuleVersion(CompilerResult compilerResult, Set sources) throws MojoExecutionException { + if (compilerResult.isSuccess() && getModuleDeclaration(sources).isPresent()) { + Path moduleDescriptor = getOutputDirectory().toPath().resolve("module-info.class"); + if (Files.isRegularFile(moduleDescriptor)) { + try { + final byte[] descriptorOriginal = Files.readAllBytes(moduleDescriptor); + final byte[] descriptorMod = + ModuleInfoTransformer.transform(descriptorOriginal, getRelease(), getLog()); + Files.write(moduleDescriptor, descriptorMod); + } catch (IOException ex) { + throw new MojoExecutionException("Error reading or writing module-info.class", ex); + } + } + } + } } diff --git a/src/main/java/org/apache/maven/plugin/compiler/CompilerMojo.java b/src/main/java/org/apache/maven/plugin/compiler/CompilerMojo.java index a364e911..9ba8a753 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/CompilerMojo.java +++ b/src/main/java/org/apache/maven/plugin/compiler/CompilerMojo.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -29,6 +30,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.apache.maven.artifact.Artifact; @@ -228,18 +230,9 @@ protected Set getExcludes() { protected void preparePaths(Set sourceFiles) { // assert compilePath != null; - File moduleDescriptorPath = null; + Optional moduleDeclaration = getModuleDeclaration(sourceFiles); - boolean hasModuleDescriptor = false; - for (File sourceFile : sourceFiles) { - if ("module-info.java".equals(sourceFile.getName())) { - moduleDescriptorPath = sourceFile; - hasModuleDescriptor = true; - break; - } - } - - if (hasModuleDescriptor) { + if (moduleDeclaration.isPresent()) { // For now only allow named modules. Once we can create a graph with ASM we can specify exactly the modules // and we can detect if auto modules are used. In that case, MavenProject.setFile() should not be used, so // you cannot depend on this project and so it won't be distributed. @@ -254,7 +247,7 @@ protected void preparePaths(Set sourceFiles) { ResolvePathsRequest request = ResolvePathsRequest.ofFiles(dependencyArtifacts) .setIncludeStatic(true) - .setMainModuleDescriptor(moduleDescriptorPath); + .setMainModuleDescriptor(moduleDeclaration.get().toFile()); Toolchain toolchain = getToolchain(); if (toolchain instanceof DefaultJavaToolChain) { diff --git a/src/main/java/org/apache/maven/plugin/compiler/ModuleInfoTransformer.java b/src/main/java/org/apache/maven/plugin/compiler/ModuleInfoTransformer.java new file mode 100644 index 00000000..2b40481d --- /dev/null +++ b/src/main/java/org/apache/maven/plugin/compiler/ModuleInfoTransformer.java @@ -0,0 +1,67 @@ +/* + * 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. + */ +package org.apache.maven.plugin.compiler; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.plugin.logging.Log; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.ModuleVisitor; +import org.objectweb.asm.Opcodes; + +final class ModuleInfoTransformer { + + private ModuleInfoTransformer() {} + + static byte[] transform(byte[] originalBytecode, String javaVersion, Log log) { + List modulesModified = new ArrayList<>(); + ClassReader reader = new ClassReader(originalBytecode); + ClassWriter writer = new ClassWriter(0); + + ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9, writer) { + @Override + public ModuleVisitor visitModule(String name, int access, String version) { + ModuleVisitor originalModuleVisitor = super.visitModule(name, access, version); + return new ModuleVisitor(Opcodes.ASM9, originalModuleVisitor) { + @Override + public void visitRequire(String module, int access, String version) { + // Check if the module name matches the java/jdk modules + if (module.startsWith("java.") || module.startsWith("jdk.")) { + // Patch the version from the java.* and jdk.* modules + // with the --release N version. + super.visitRequire(module, access, javaVersion); + modulesModified.add(module); + } else { + // Keep the original require statement + super.visitRequire(module, access, version); + } + } + }; + } + }; + + reader.accept(classVisitor, 0); + + log.info(String.format("Patch module-info.class %s with version %s", modulesModified, javaVersion)); + return writer.toByteArray(); + } +}