Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling jars without manifest #2

Merged
merged 2 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ val functionalTestSourceSet = sourceSets.create("functionalTest") {
gradlePlugin.testSourceSets(functionalTestSourceSet)
configurations.getByName("functionalTestImplementation").extendsFrom(configurations.getByName("testImplementation"))
val functionalTest by tasks.creating(Test::class) {
group = "verification"
testClassesDirs = functionalTestSourceSet.output.classesDirs
classpath = functionalTestSourceSet.runtimeClasspath
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,124 @@ class ExtraJavaModuleInfoTest extends Specification {
build().task(':compileJava').outcome == TaskOutcome.SUCCESS
}

def "can add Automatic-Module-Name to libraries without MANIFEST.MF"() {
given:
new File(testFolder.root, "src/main/java/org/gradle/sample/app/data").mkdirs()

testFolder.newFile("src/main/java/org/gradle/sample/app/Main.java") << """
package org.gradle.sample.app;

import javax.inject.Singleton;

public class Main {

public static void main(String[] args) {
printModuleDebug(Singleton.class);
}

private static void printModuleDebug(Class<?> clazz) {
System.out.println(clazz.getModule().getName() + " - " + clazz.getModule().getDescriptor().version().get());
}

}
"""
testFolder.newFile("src/main/java/module-info.java") << """
module org.gradle.sample.app {
requires javax.inject;
}
"""
testFolder.newFile("build.gradle.kts") << """
plugins {
application
id("de.jjohannes.extra-java-module-info")
}

repositories {
mavenCentral()
}

java {
modularity.inferModulePath.set(true)
}

dependencies {
implementation("javax.inject:javax.inject:1")
}

extraJavaModuleInfo {
automaticModule("javax.inject-1.jar", "javax.inject")
}

application {
mainModule.set("org.gradle.sample.app")
mainClass.set("org.gradle.sample.app.Main")
}
"""

expect:
build().task(':compileJava').outcome == TaskOutcome.SUCCESS
}

def "can add module-info.class to libraries without MANIFEST.MF"() {
given:
new File(testFolder.root, "src/main/java/org/gradle/sample/app/data").mkdirs()

testFolder.newFile("src/main/java/org/gradle/sample/app/Main.java") << """
package org.gradle.sample.app;

import javax.inject.Singleton;

public class Main {

public static void main(String[] args) {
printModuleDebug(Singleton.class);
}

private static void printModuleDebug(Class<?> clazz) {
System.out.println(clazz.getModule().getName() + " - " + clazz.getModule().getDescriptor().version().get());
}

}
"""
testFolder.newFile("src/main/java/module-info.java") << """
module org.gradle.sample.app {
requires javax.inject;
}
"""
testFolder.newFile("build.gradle.kts") << """
plugins {
application
id("de.jjohannes.extra-java-module-info")
}

repositories {
mavenCentral()
}

java {
modularity.inferModulePath.set(true)
}

dependencies {
implementation("javax.inject:javax.inject:1")
}

extraJavaModuleInfo {
module("javax.inject-1.jar", "javax.inject", "") {
exports("javax.inject")
}
}

application {
mainModule.set("org.gradle.sample.app")
mainClass.set("org.gradle.sample.app.Main")
}
"""

expect:
build().task(':compileJava').outcome == TaskOutcome.SUCCESS
}

BuildResult build() {
gradleRunnerFor(['build']).build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.io.*;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.jar.*;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
Expand All @@ -40,6 +41,8 @@
*/
abstract public class ExtraModuleInfoTransform implements TransformAction<ExtraModuleInfoTransform.Parameter> {

private static final Pattern MODULE_INFO_CLASS_MRJAR_PATH = Pattern.compile("META-INF/versions/\\d+/module-info.class");

public static class Parameter implements TransformParameters, Serializable {
private Map<String, ModuleInfo> moduleInfo = Collections.emptyMap();
private Map<String, String> automaticModules = Collections.emptyMap();
Expand Down Expand Up @@ -80,22 +83,21 @@ public void transform(TransformOutputs outputs) {
} else if (isAutoModule(originalJar)) {
outputs.file(originalJar);
} else if (automaticModules.containsKey(originalJarName)) {
addAutomaticModuleName(originalJar, getModuleJar(outputs, originalJar), automaticModules.get(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()) {
if (isMultiReleaseJar && MODULE_INFO_CLASS_MRJAR_PATH.matcher(next.getName()).matches()) {
return true;
}
next = inputStream.getNextEntry();
Expand All @@ -113,7 +115,8 @@ private boolean containsMultiReleaseJarEntry(JarInputStream jarStream) {

private boolean isAutoModule(File jar) {
try (JarInputStream inputStream = new JarInputStream(new FileInputStream(jar))) {
return inputStream.getManifest().getMainAttributes().getValue("Automatic-Module-Name") != null;
Manifest manifest = inputStream.getManifest();
return manifest != null && manifest.getMainAttributes().getValue("Automatic-Module-Name") != null;
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand All @@ -126,8 +129,12 @@ private File getModuleJar(TransformOutputs outputs, File originalJar) {
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())) {
if (manifest == null) {
manifest = new Manifest();
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
}
manifest.getMainAttributes().putValue("Automatic-Module-Name", moduleName);
try (JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(moduleJar), manifest)) {
copyEntries(inputStream, outputStream);
}
} catch (IOException e) {
Expand All @@ -137,7 +144,7 @@ private static void addAutomaticModuleName(File originalJar, File moduleJar, Str

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())) {
try (JarOutputStream outputStream = newJarOutputStream(new FileOutputStream(moduleJar), inputStream.getManifest())) {
copyEntries(inputStream, outputStream);
outputStream.putNextEntry(new JarEntry("module-info.class"));
outputStream.write(addModuleInfo(moduleInfo));
Expand All @@ -148,6 +155,10 @@ private static void addModuleDescriptor(File originalJar, File moduleJar, Module
}
}

private static JarOutputStream newJarOutputStream(OutputStream out, Manifest manifest) throws IOException {
return manifest == null ? new JarOutputStream(out) : new JarOutputStream(out, manifest);
}

private static void copyEntries(JarInputStream inputStream, JarOutputStream outputStream) throws IOException {
JarEntry jarEntry = inputStream.getNextJarEntry();
while (jarEntry != null) {
Expand Down