-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
370 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,23 @@ | ||
plugins { | ||
id 'java-library' | ||
id 'extra-java-module-info' | ||
} | ||
|
||
dependencies { | ||
// https://mvnrepository.com/artifact/com.jayway.jsonpath/json-path | ||
implementation "com.jayway.jsonpath:json-path:$jsonPathVersion" | ||
} | ||
|
||
tasks.jar { | ||
manifest { | ||
attributes["Automatic-Module-Name"] = "com.tersesystems.echopraxia.api" | ||
// https://docs.gradle.org/current/samples/sample_java_modules_with_transform.html | ||
extraJavaModuleInfo { | ||
module("json-path-$jsonPathVersion" + '.jar', 'com.jayway.jsonpath', jsonPathVersion) { | ||
requires("org.slf4j.slf4j-api") | ||
requires("net.minidev.json-smart") | ||
|
||
exports('com.jayway.jsonpath') | ||
exports('com.jayway.jsonpath.spi.json') | ||
exports('com.jayway.jsonpath.spi.mapper') | ||
} | ||
module('slf4j-api-1.7.36.jar', 'org.slf4j.slf4j-api', '1.7.36') | ||
module('json-smart-2.4.10.jar', 'net.minidev.json-smart', '2.4.10') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
module com.tersesystems.echopraxia.api { | ||
requires json.path; | ||
requires org.jetbrains.annotations; | ||
requires static transitive org.jetbrains.annotations; | ||
requires com.jayway.jsonpath; | ||
|
||
exports com.tersesystems.echopraxia.api; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
plugins { | ||
id 'java-gradle-plugin' // so we can assign and ID to our plugin | ||
} | ||
|
||
dependencies { | ||
implementation 'org.ow2.asm:asm:8.0.1' | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
gradlePlugin { | ||
plugins { | ||
// here we register our plugin with an ID | ||
register("extra-java-module-info") { | ||
id = "extra-java-module-info" | ||
implementationClass = "org.gradle.sample.transform.javamodules.ExtraModuleInfoPlugin" | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoPlugin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package org.gradle.sample.transform.javamodules; | ||
|
||
import org.gradle.api.Plugin; | ||
import org.gradle.api.Project; | ||
import org.gradle.api.artifacts.Configuration; | ||
import org.gradle.api.attributes.Attribute; | ||
import org.gradle.api.plugins.JavaPlugin; | ||
|
||
/** | ||
* Entry point of our plugin that should be applied in the root project. | ||
*/ | ||
public class ExtraModuleInfoPlugin implements Plugin<Project> { | ||
|
||
@Override | ||
public void apply(Project project) { | ||
// register the plugin extension as 'extraJavaModuleInfo {}' configuration block | ||
ExtraModuleInfoPluginExtension extension = project.getObjects().newInstance(ExtraModuleInfoPluginExtension.class); | ||
project.getExtensions().add(ExtraModuleInfoPluginExtension.class, "extraJavaModuleInfo", extension); | ||
|
||
// setup the transform for all projects in the build | ||
project.getPlugins().withType(JavaPlugin.class).configureEach(javaPlugin -> configureTransform(project, extension)); | ||
} | ||
|
||
private void configureTransform(Project project, ExtraModuleInfoPluginExtension extension) { | ||
Attribute<String> artifactType = Attribute.of("artifactType", String.class); | ||
Attribute<Boolean> javaModule = Attribute.of("javaModule", Boolean.class); | ||
|
||
// compile and runtime classpath express that they only accept modules by requesting the javaModule=true attribute | ||
project.getConfigurations().matching(this::isResolvingJavaPluginConfiguration).all( | ||
c -> c.getAttributes().attribute(javaModule, true)); | ||
|
||
// all Jars have a javaModule=false attribute by default; the transform also recognizes modules and returns them without modification | ||
project.getDependencies().getArtifactTypes().getByName("jar").getAttributes().attribute(javaModule, false); | ||
|
||
// register the transform for Jars and "javaModule=false -> javaModule=true"; the plugin extension object fills the input parameter | ||
project.getDependencies().registerTransform(ExtraModuleInfoTransform.class, t -> { | ||
t.parameters(p -> { | ||
p.setModuleInfo(extension.getModuleInfo()); | ||
p.setAutomaticModules(extension.getAutomaticModules()); | ||
}); | ||
t.getFrom().attribute(artifactType, "jar").attribute(javaModule, false); | ||
t.getTo().attribute(artifactType, "jar").attribute(javaModule, true); | ||
}); | ||
} | ||
|
||
private boolean isResolvingJavaPluginConfiguration(Configuration configuration) { | ||
if (!configuration.isCanBeResolved()) { | ||
return false; | ||
} | ||
return configuration.getName().endsWith(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME.substring(1)) | ||
|| configuration.getName().endsWith(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME.substring(1)) | ||
|| configuration.getName().endsWith(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME.substring(1)); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
...src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoPluginExtension.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package org.gradle.sample.transform.javamodules; | ||
|
||
|
||
import org.gradle.api.Action; | ||
|
||
import javax.annotation.Nullable; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* A data class to collect all the module information we want to add. | ||
* Here the class is used as extension that can be configured in the build script | ||
* and as input to the ExtraModuleInfoTransform that add the information to Jars. | ||
*/ | ||
public class ExtraModuleInfoPluginExtension { | ||
|
||
private final Map<String, ModuleInfo> moduleInfo = new HashMap<>(); | ||
private final Map<String, String> automaticModules = new HashMap<>(); | ||
|
||
/** | ||
* Add full module information for a given Jar file. | ||
*/ | ||
public void module(String jarName, String moduleName, String moduleVersion) { | ||
module(jarName, moduleName, moduleVersion, null); | ||
} | ||
|
||
/** | ||
* Add full module information, including exported packages and dependencies, for a given Jar file. | ||
*/ | ||
public void module(String jarName, String moduleName, String moduleVersion, @Nullable Action<? super ModuleInfo> conf) { | ||
ModuleInfo moduleInfo = new ModuleInfo(moduleName, moduleVersion); | ||
if (conf != null) { | ||
conf.execute(moduleInfo); | ||
} | ||
this.moduleInfo.put(jarName, moduleInfo); | ||
} | ||
|
||
/** | ||
* Add only an automatic module name to a given jar file. | ||
*/ | ||
public void automaticModule(String jarName, String moduleName) { | ||
automaticModules.put(jarName, moduleName); | ||
} | ||
|
||
protected Map<String, ModuleInfo> getModuleInfo() { | ||
return moduleInfo; | ||
} | ||
|
||
protected Map<String, String> getAutomaticModules() { | ||
return automaticModules; | ||
} | ||
} |
164 changes: 164 additions & 0 deletions
164
buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoTransform.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package org.gradle.sample.transform.javamodules; | ||
|
||
import org.gradle.api.artifacts.transform.InputArtifact; | ||
import org.gradle.api.artifacts.transform.TransformAction; | ||
import org.gradle.api.artifacts.transform.TransformOutputs; | ||
import org.gradle.api.artifacts.transform.TransformParameters; | ||
import org.gradle.api.file.FileSystemLocation; | ||
import org.gradle.api.provider.Provider; | ||
import org.gradle.api.tasks.Input; | ||
import org.objectweb.asm.ClassWriter; | ||
import org.objectweb.asm.ModuleVisitor; | ||
import org.objectweb.asm.Opcodes; | ||
|
||
import java.io.*; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.jar.*; | ||
import java.util.regex.Pattern; | ||
import java.util.zip.ZipEntry; | ||
|
||
/** | ||
* An artifact transform that applies additional information to Jars without module information. | ||
* The transformation fails the build if a Jar does not contain information and no extra information | ||
* was defined for it. This way we make sure that all Jars are turned into modules. | ||
*/ | ||
abstract public class ExtraModuleInfoTransform implements TransformAction<ExtraModuleInfoTransform.Parameter> { | ||
|
||
public static class Parameter implements TransformParameters, Serializable { | ||
private Map<String, ModuleInfo> moduleInfo = Collections.emptyMap(); | ||
private Map<String, String> automaticModules = Collections.emptyMap(); | ||
|
||
@Input | ||
public Map<String, ModuleInfo> getModuleInfo() { | ||
return moduleInfo; | ||
} | ||
|
||
@Input | ||
public Map<String, String> getAutomaticModules() { | ||
return automaticModules; | ||
} | ||
|
||
public void setModuleInfo(Map<String, ModuleInfo> moduleInfo) { | ||
this.moduleInfo = moduleInfo; | ||
} | ||
|
||
public void setAutomaticModules(Map<String, String> automaticModules) { | ||
this.automaticModules = automaticModules; | ||
} | ||
} | ||
|
||
@InputArtifact | ||
protected abstract Provider<FileSystemLocation> getInputArtifact(); | ||
|
||
@Override | ||
public void transform(TransformOutputs outputs) { | ||
Map<String, ModuleInfo> moduleInfo = getParameters().moduleInfo; | ||
Map<String, String> automaticModules = getParameters().automaticModules; | ||
File originalJar = getInputArtifact().get().getAsFile(); | ||
String originalJarName = originalJar.getName(); | ||
|
||
if (isModule(originalJar)) { | ||
outputs.file(originalJar); | ||
} else if (moduleInfo.containsKey(originalJarName)) { | ||
addModuleDescriptor(originalJar, getModuleJar(outputs, originalJar), moduleInfo.get(originalJarName)); | ||
} else if (isAutoModule(originalJar)) { | ||
outputs.file(originalJar); | ||
} else if (automaticModules.containsKey(originalJarName)) { | ||
addAutomaticModuleName(originalJar, getModuleJar(outputs, originalJar), automaticModules.get(originalJarName)); | ||
} else { | ||
throw new RuntimeException("Not a module and no mapping defined: " + originalJarName); | ||
} | ||
} | ||
|
||
private boolean isModule(File jar) { | ||
Pattern moduleInfoClassMrjarPath = Pattern.compile("META-INF/versions/\\d+/module-info.class"); | ||
try (JarInputStream inputStream = new JarInputStream(new FileInputStream(jar))) { | ||
boolean isMultiReleaseJar = containsMultiReleaseJarEntry(inputStream); | ||
ZipEntry next = inputStream.getNextEntry(); | ||
while (next != null) { | ||
if ("module-info.class".equals(next.getName())) { | ||
return true; | ||
} | ||
if (isMultiReleaseJar && moduleInfoClassMrjarPath.matcher(next.getName()).matches()) { | ||
return true; | ||
} | ||
next = inputStream.getNextEntry(); | ||
} | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
return false; | ||
} | ||
|
||
private boolean containsMultiReleaseJarEntry(JarInputStream jarStream) { | ||
Manifest manifest = jarStream.getManifest(); | ||
return manifest != null && Boolean.parseBoolean(manifest.getMainAttributes().getValue("Multi-Release")); | ||
} | ||
|
||
private boolean isAutoModule(File jar) { | ||
try (JarInputStream inputStream = new JarInputStream(new FileInputStream(jar))) { | ||
return inputStream.getManifest().getMainAttributes().getValue("Automatic-Module-Name") != null; | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private File getModuleJar(TransformOutputs outputs, File originalJar) { | ||
return outputs.file(originalJar.getName().substring(0, originalJar.getName().lastIndexOf('.')) + "-module.jar"); | ||
} | ||
|
||
private static void addAutomaticModuleName(File originalJar, File moduleJar, String moduleName) { | ||
try (JarInputStream inputStream = new JarInputStream(new FileInputStream(originalJar))) { | ||
Manifest manifest = inputStream.getManifest(); | ||
manifest.getMainAttributes().put(new Attributes.Name("Automatic-Module-Name"), moduleName); | ||
try (JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(moduleJar), inputStream.getManifest())) { | ||
copyEntries(inputStream, outputStream); | ||
} | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static void addModuleDescriptor(File originalJar, File moduleJar, ModuleInfo moduleInfo) { | ||
try (JarInputStream inputStream = new JarInputStream(new FileInputStream(originalJar))) { | ||
try (JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(moduleJar), inputStream.getManifest())) { | ||
copyEntries(inputStream, outputStream); | ||
outputStream.putNextEntry(new JarEntry("module-info.class")); | ||
outputStream.write(addModuleInfo(moduleInfo)); | ||
outputStream.closeEntry(); | ||
} | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static void copyEntries(JarInputStream inputStream, JarOutputStream outputStream) throws IOException { | ||
JarEntry jarEntry = inputStream.getNextJarEntry(); | ||
while (jarEntry != null) { | ||
outputStream.putNextEntry(jarEntry); | ||
outputStream.write(inputStream.readAllBytes()); | ||
outputStream.closeEntry(); | ||
jarEntry = inputStream.getNextJarEntry(); | ||
} | ||
} | ||
|
||
private static byte[] addModuleInfo(ModuleInfo moduleInfo) { | ||
ClassWriter classWriter = new ClassWriter(0); | ||
classWriter.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null, null); | ||
ModuleVisitor moduleVisitor = classWriter.visitModule(moduleInfo.getModuleName(), Opcodes.ACC_OPEN, moduleInfo.getModuleVersion()); | ||
for (String packageName : moduleInfo.getExports()) { | ||
moduleVisitor.visitExport(packageName.replace('.', '/'), 0); | ||
} | ||
moduleVisitor.visitRequire("java.base", 0, null); | ||
for (String requireName : moduleInfo.getRequires()) { | ||
moduleVisitor.visitRequire(requireName, 0, null); | ||
} | ||
for (String requireName : moduleInfo.getRequiresTransitive()) { | ||
moduleVisitor.visitRequire(requireName, Opcodes.ACC_TRANSITIVE, null); | ||
} | ||
moduleVisitor.visitEnd(); | ||
classWriter.visitEnd(); | ||
return classWriter.toByteArray(); | ||
} | ||
} |
Oops, something went wrong.