Skip to content

Commit

Permalink
[MCOMPILER-542] Clean JDK patch version in module-info.class
Browse files Browse the repository at this point in the history
Signed-off-by: Jorge Solórzano <jorsol@gmail.com>
  • Loading branch information
jorsol authored and hboutemy committed Nov 28, 2023
1 parent 340f63c commit 8420d58
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 97 deletions.
2 changes: 1 addition & 1 deletion src/it/MCOMPILER-542/invoker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
# specific language governing permissions and limitations
# under the License.

invoker.java.version = 9+
invoker.java.version = 11+
108 changes: 58 additions & 50 deletions src/it/MCOMPILER-542/pom.xml
Original file line number Diff line number Diff line change
@@ -1,50 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.apache.maven.plugins.compiler.it</groupId>
<artifactId>MCOMPILER-542</artifactId>
<version>1.0-SNAPSHOT</version>
<name>${java.specification.version}</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2023-08-14T15:12:12Z</project.build.outputTimestamp>
</properties>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>@project.version@</version>
<configuration>
<release>${java.specification.version}</release>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.apache.maven.plugins.compiler.it</groupId>
<artifactId>MCOMPILER-542</artifactId>
<version>1.0-SNAPSHOT</version>
<name>${java.specification.version}</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2023-08-14T15:12:12Z</project.build.outputTimestamp>
</properties>

<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk-platform-logging</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>@project.version@</version>
<configuration>
<release>${java.specification.version}</release>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
6 changes: 5 additions & 1 deletion src/it/MCOMPILER-542/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
3 changes: 0 additions & 3 deletions src/it/MCOMPILER-542/src/main/java/org/maven/test/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@

public class Main {

/**
* @param args
*/
public static void main(String[] args) {
System.out.println("Hello World!");
}
Expand Down
91 changes: 61 additions & 30 deletions src/it/MCOMPILER-542/verify.groovy
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,15 @@ protected final MavenProject getProject() {
return project;
}

protected final Optional<Path> getModuleDeclaration(final Set<File> sourceFiles) {
for (File sourceFile : sourceFiles) {
if ("module-info.java".equals(sourceFile.getName())) {
return Optional.of(sourceFile.toPath());
}
}
return Optional.empty();
}

private boolean targetOrReleaseSet;

@Override
Expand Down Expand Up @@ -1177,6 +1186,8 @@ public void execute() throws MojoExecutionException, CompilationFailureException
}
}

patchJdkModuleVersion(compilerResult, sources);

if (useIncrementalCompilation) {
if (incrementalBuildHelperRequest.getOutputDirectory().exists()) {
getLog().debug("incrementalBuildHelper#afterRebuildExecution");
Expand Down Expand Up @@ -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 <a href="https://issues.apache.org/jira/browse/MCOMPILER-542">MCOMPILER-542</a>
* @see <a href="https://bugs.openjdk.org/browse/JDK-8318913">JDK-8318913</a>
*/
private void patchJdkModuleVersion(CompilerResult compilerResult, Set<File> 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);
}
}
}
}
}
17 changes: 5 additions & 12 deletions src/main/java/org/apache/maven/plugin/compiler/CompilerMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -228,18 +230,9 @@ protected Set<String> getExcludes() {
protected void preparePaths(Set<File> sourceFiles) {
// assert compilePath != null;

File moduleDescriptorPath = null;
Optional<Path> 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.
Expand All @@ -254,7 +247,7 @@ protected void preparePaths(Set<File> sourceFiles) {

ResolvePathsRequest<File> request = ResolvePathsRequest.ofFiles(dependencyArtifacts)
.setIncludeStatic(true)
.setMainModuleDescriptor(moduleDescriptorPath);
.setMainModuleDescriptor(moduleDeclaration.get().toFile());

Toolchain toolchain = getToolchain();
if (toolchain instanceof DefaultJavaToolChain) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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();
}
}

0 comments on commit 8420d58

Please sign in to comment.