Skip to content

Commit 8420d58

Browse files
jorsolhboutemy
authored andcommitted
[MCOMPILER-542] Clean JDK patch version in module-info.class
Signed-off-by: Jorge Solórzano <[email protected]>
1 parent 340f63c commit 8420d58

File tree

8 files changed

+233
-97
lines changed

8 files changed

+233
-97
lines changed

src/it/MCOMPILER-542/invoker.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18-
invoker.java.version = 9+
18+
invoker.java.version = 11+

src/it/MCOMPILER-542/pom.xml

+58-50
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,58 @@
1-
<?xml version="1.0" encoding="UTF-8"?>
2-
<!--
3-
~ Licensed to the Apache Software Foundation (ASF) under one
4-
~ or more contributor license agreements. See the NOTICE file
5-
~ distributed with this work for additional information
6-
~ regarding copyright ownership. The ASF licenses this file
7-
~ to you under the Apache License, Version 2.0 (the
8-
~ "License"); you may not use this file except in compliance
9-
~ with the License. You may obtain a copy of the License at
10-
~
11-
~ http://www.apache.org/licenses/LICENSE-2.0
12-
~
13-
~ Unless required by applicable law or agreed to in writing,
14-
~ software distributed under the License is distributed on an
15-
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16-
~ KIND, either express or implied. See the License for the
17-
~ specific language governing permissions and limitations
18-
~ under the License.
19-
-->
20-
21-
<project xmlns="http://maven.apache.org/POM/4.0.0"
22-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23-
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
24-
<modelVersion>4.0.0</modelVersion>
25-
26-
<groupId>org.apache.maven.plugins.compiler.it</groupId>
27-
<artifactId>MCOMPILER-542</artifactId>
28-
<version>1.0-SNAPSHOT</version>
29-
<name>${java.specification.version}</name>
30-
31-
<properties>
32-
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
33-
<project.build.outputTimestamp>2023-08-14T15:12:12Z</project.build.outputTimestamp>
34-
</properties>
35-
36-
<build>
37-
<pluginManagement>
38-
<plugins>
39-
<plugin>
40-
<groupId>org.apache.maven.plugins</groupId>
41-
<artifactId>maven-compiler-plugin</artifactId>
42-
<version>@project.version@</version>
43-
<configuration>
44-
<release>${java.specification.version}</release>
45-
</configuration>
46-
</plugin>
47-
</plugins>
48-
</pluginManagement>
49-
</build>
50-
</project>
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Licensed to the Apache Software Foundation (ASF) under one
4+
~ or more contributor license agreements. See the NOTICE file
5+
~ distributed with this work for additional information
6+
~ regarding copyright ownership. The ASF licenses this file
7+
~ to you under the Apache License, Version 2.0 (the
8+
~ "License"); you may not use this file except in compliance
9+
~ with the License. You may obtain a copy of the License at
10+
~
11+
~ http://www.apache.org/licenses/LICENSE-2.0
12+
~
13+
~ Unless required by applicable law or agreed to in writing,
14+
~ software distributed under the License is distributed on an
15+
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
~ KIND, either express or implied. See the License for the
17+
~ specific language governing permissions and limitations
18+
~ under the License.
19+
-->
20+
21+
<project xmlns="http://maven.apache.org/POM/4.0.0"
22+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
24+
<modelVersion>4.0.0</modelVersion>
25+
26+
<groupId>org.apache.maven.plugins.compiler.it</groupId>
27+
<artifactId>MCOMPILER-542</artifactId>
28+
<version>1.0-SNAPSHOT</version>
29+
<name>${java.specification.version}</name>
30+
31+
<properties>
32+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
33+
<project.build.outputTimestamp>2023-08-14T15:12:12Z</project.build.outputTimestamp>
34+
</properties>
35+
36+
<dependencies>
37+
<dependency>
38+
<groupId>org.slf4j</groupId>
39+
<artifactId>slf4j-jdk-platform-logging</artifactId>
40+
<version>2.0.9</version>
41+
</dependency>
42+
</dependencies>
43+
44+
<build>
45+
<pluginManagement>
46+
<plugins>
47+
<plugin>
48+
<groupId>org.apache.maven.plugins</groupId>
49+
<artifactId>maven-compiler-plugin</artifactId>
50+
<version>@project.version@</version>
51+
<configuration>
52+
<release>${java.specification.version}</release>
53+
</configuration>
54+
</plugin>
55+
</plugins>
56+
</pluginManagement>
57+
</build>
58+
</project>

src/it/MCOMPILER-542/src/main/java/module-info.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
module app {}
19+
module app {
20+
requires java.logging;
21+
requires jdk.zipfs;
22+
requires org.slf4j.jdk.platform.logging;
23+
}

src/it/MCOMPILER-542/src/main/java/org/maven/test/Main.java

-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020

2121
public class Main {
2222

23-
/**
24-
* @param args
25-
*/
2623
public static void main(String[] args) {
2724
System.out.println("Hello World!");
2825
}

src/it/MCOMPILER-542/verify.groovy

+61-30
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,61 @@
1-
/*
2-
* Licensed to the Apache Software Foundation (ASF) under one
3-
* or more contributor license agreements. See the NOTICE file
4-
* distributed with this work for additional information
5-
* regarding copyright ownership. The ASF licenses this file
6-
* to you under the Apache License, Version 2.0 (the
7-
* "License"); you may not use this file except in compliance
8-
* with the License. You may obtain a copy of the License at
9-
*
10-
* http://www.apache.org/licenses/LICENSE-2.0
11-
*
12-
* Unless required by applicable law or agreed to in writing,
13-
* software distributed under the License is distributed on an
14-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15-
* KIND, either express or implied. See the License for the
16-
* specific language governing permissions and limitations
17-
* under the License.
18-
*/
19-
20-
def proc = 'javap -v target/classes/module-info.class'.execute(null,basedir)
21-
def sout = new StringBuilder(), serr = new StringBuilder()
22-
proc.consumeProcessOutput(sout, serr)
23-
proc.waitForOrKill(1000)
24-
def out = sout.toString()
25-
println "javap -v target/classes/module-info.class>\n$out\nerr> $serr"
26-
27-
def module = out.substring(out.indexOf('Module:'))
28-
def javaVersion = System.getProperty('java.version')
29-
assert module.contains('// "java.base" ACC_MANDATED')
30-
assert !module.contains(javaVersion)
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
// Check if the javap tool is available
21+
def javapTool = java.util.spi.ToolProvider.findFirst("javap")
22+
assert javapTool.isPresent() : "javap tool not found. Make sure you have the JDK installed."
23+
24+
def moduleDescriptor = new File(basedir, "target/classes/module-info.class")
25+
// Create a list of arguments to pass to the javap tool
26+
String[] args = ["-v", moduleDescriptor]
27+
28+
def swout = new StringWriter(), swerr = new StringWriter()
29+
// Execute the javap tool with args
30+
def result = javapTool.get().run(new PrintWriter(swout), new PrintWriter(swerr), args)
31+
println swerr.toString().isEmpty() ? "javap output:\n$swout" : "javap error:\n$swerr"
32+
assert (result == 0) : "javap run failed"
33+
34+
// Assertions of module content
35+
def out = swout.toString()
36+
assert out.contains('// "java.base" ACC_MANDATED') : "module not found in module-info.class"
37+
assert out.contains('// "java.logging"') : "module not found in module-info.class"
38+
assert out.contains('// "jdk.zipfs"') : "module not found in module-info.class"
39+
assert out.contains('// "org.slf4j.jdk.platform.logging"') : "module not found in module-info.class"
40+
assert out.contains('// 2.0.9') : "version of org.slf4j.jdk.platform.logging module not found"
41+
42+
// Validation that the module-info should not contain the full java version but the spec version.
43+
def javaVersion = System.getProperty('java.version')
44+
def javaSpecVersion = System.getProperty('java.specification.version')
45+
if (javaVersion != javaSpecVersion) { // handle the case when is the first release
46+
assert !out.contains('// ' + javaVersion) : "full java version found in module descriptor"
47+
}
48+
assert out.contains('// ' + javaSpecVersion) : "java specification version not found in module descriptor"
49+
50+
// Additional validation that the checksum is always the same
51+
def checksumMap = [
52+
'21': 'SHA-256 checksum ccc6515c8fc1bf4e675e205b2a5200d02545b06014b304c292eeddc68cffee8d',
53+
'17': 'SHA-256 checksum 102f24c71aff97210d66ef791b7d56f8a25ff8692d2c97b21682bc7170aaca9c',
54+
'11': 'MD5 checksum 5779cc6044dcba6ae4060e5a2f8a32c8'
55+
]
56+
57+
def expectedChecksum = checksumMap[javaSpecVersion]
58+
if (expectedChecksum) {
59+
println "Java version: $javaVersion"
60+
assert out.contains(expectedChecksum) : "checksum doesn't match expected output"
61+
}

src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java

+36
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,15 @@ protected final MavenProject getProject() {
644644
return project;
645645
}
646646

647+
protected final Optional<Path> getModuleDeclaration(final Set<File> sourceFiles) {
648+
for (File sourceFile : sourceFiles) {
649+
if ("module-info.java".equals(sourceFile.getName())) {
650+
return Optional.of(sourceFile.toPath());
651+
}
652+
}
653+
return Optional.empty();
654+
}
655+
647656
private boolean targetOrReleaseSet;
648657

649658
@Override
@@ -1177,6 +1186,8 @@ public void execute() throws MojoExecutionException, CompilationFailureException
11771186
}
11781187
}
11791188

1189+
patchJdkModuleVersion(compilerResult, sources);
1190+
11801191
if (useIncrementalCompilation) {
11811192
if (incrementalBuildHelperRequest.getOutputDirectory().exists()) {
11821193
getLog().debug("incrementalBuildHelper#afterRebuildExecution");
@@ -1798,4 +1809,29 @@ public void setRelease(String release) {
17981809
final String getImplicit() {
17991810
return implicit;
18001811
}
1812+
1813+
/**
1814+
* Patch module-info.class to set the java release version for java/jdk modules.
1815+
*
1816+
* @param compilerResult should succeed.
1817+
* @param sources the list of the source files to check for the "module-info.java"
1818+
*
1819+
* @see <a href="https://issues.apache.org/jira/browse/MCOMPILER-542">MCOMPILER-542</a>
1820+
* @see <a href="https://bugs.openjdk.org/browse/JDK-8318913">JDK-8318913</a>
1821+
*/
1822+
private void patchJdkModuleVersion(CompilerResult compilerResult, Set<File> sources) throws MojoExecutionException {
1823+
if (compilerResult.isSuccess() && getModuleDeclaration(sources).isPresent()) {
1824+
Path moduleDescriptor = getOutputDirectory().toPath().resolve("module-info.class");
1825+
if (Files.isRegularFile(moduleDescriptor)) {
1826+
try {
1827+
final byte[] descriptorOriginal = Files.readAllBytes(moduleDescriptor);
1828+
final byte[] descriptorMod =
1829+
ModuleInfoTransformer.transform(descriptorOriginal, getRelease(), getLog());
1830+
Files.write(moduleDescriptor, descriptorMod);
1831+
} catch (IOException ex) {
1832+
throw new MojoExecutionException("Error reading or writing module-info.class", ex);
1833+
}
1834+
}
1835+
}
1836+
}
18011837
}

src/main/java/org/apache/maven/plugin/compiler/CompilerMojo.java

+5-12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.io.File;
2222
import java.io.IOException;
23+
import java.nio.file.Path;
2324
import java.util.ArrayList;
2425
import java.util.Collection;
2526
import java.util.Collections;
@@ -29,6 +30,7 @@
2930
import java.util.Map;
3031
import java.util.Map.Entry;
3132
import java.util.Objects;
33+
import java.util.Optional;
3234
import java.util.Set;
3335

3436
import org.apache.maven.artifact.Artifact;
@@ -228,18 +230,9 @@ protected Set<String> getExcludes() {
228230
protected void preparePaths(Set<File> sourceFiles) {
229231
// assert compilePath != null;
230232

231-
File moduleDescriptorPath = null;
233+
Optional<Path> moduleDeclaration = getModuleDeclaration(sourceFiles);
232234

233-
boolean hasModuleDescriptor = false;
234-
for (File sourceFile : sourceFiles) {
235-
if ("module-info.java".equals(sourceFile.getName())) {
236-
moduleDescriptorPath = sourceFile;
237-
hasModuleDescriptor = true;
238-
break;
239-
}
240-
}
241-
242-
if (hasModuleDescriptor) {
235+
if (moduleDeclaration.isPresent()) {
243236
// For now only allow named modules. Once we can create a graph with ASM we can specify exactly the modules
244237
// and we can detect if auto modules are used. In that case, MavenProject.setFile() should not be used, so
245238
// you cannot depend on this project and so it won't be distributed.
@@ -254,7 +247,7 @@ protected void preparePaths(Set<File> sourceFiles) {
254247

255248
ResolvePathsRequest<File> request = ResolvePathsRequest.ofFiles(dependencyArtifacts)
256249
.setIncludeStatic(true)
257-
.setMainModuleDescriptor(moduleDescriptorPath);
250+
.setMainModuleDescriptor(moduleDeclaration.get().toFile());
258251

259252
Toolchain toolchain = getToolchain();
260253
if (toolchain instanceof DefaultJavaToolChain) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.plugin.compiler;
20+
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
24+
import org.apache.maven.plugin.logging.Log;
25+
import org.objectweb.asm.ClassReader;
26+
import org.objectweb.asm.ClassVisitor;
27+
import org.objectweb.asm.ClassWriter;
28+
import org.objectweb.asm.ModuleVisitor;
29+
import org.objectweb.asm.Opcodes;
30+
31+
final class ModuleInfoTransformer {
32+
33+
private ModuleInfoTransformer() {}
34+
35+
static byte[] transform(byte[] originalBytecode, String javaVersion, Log log) {
36+
List<String> modulesModified = new ArrayList<>();
37+
ClassReader reader = new ClassReader(originalBytecode);
38+
ClassWriter writer = new ClassWriter(0);
39+
40+
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9, writer) {
41+
@Override
42+
public ModuleVisitor visitModule(String name, int access, String version) {
43+
ModuleVisitor originalModuleVisitor = super.visitModule(name, access, version);
44+
return new ModuleVisitor(Opcodes.ASM9, originalModuleVisitor) {
45+
@Override
46+
public void visitRequire(String module, int access, String version) {
47+
// Check if the module name matches the java/jdk modules
48+
if (module.startsWith("java.") || module.startsWith("jdk.")) {
49+
// Patch the version from the java.* and jdk.* modules
50+
// with the --release N version.
51+
super.visitRequire(module, access, javaVersion);
52+
modulesModified.add(module);
53+
} else {
54+
// Keep the original require statement
55+
super.visitRequire(module, access, version);
56+
}
57+
}
58+
};
59+
}
60+
};
61+
62+
reader.accept(classVisitor, 0);
63+
64+
log.info(String.format("Patch module-info.class %s with version %s", modulesModified, javaVersion));
65+
return writer.toByteArray();
66+
}
67+
}

0 commit comments

Comments
 (0)