Skip to content

Commit

Permalink
Adding option allowModuleInfos() to make it optional to include modul…
Browse files Browse the repository at this point in the history
…e-info.class files easily
  • Loading branch information
Jan Diederich committed Sep 18, 2024
1 parent 95a4326 commit 3ed4f3a
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class ShadowJavaPlugin implements Plugin<Project> {
public static final String SHADOW_GROUP = 'Shadow'
public static final String SHADOW_RUNTIME_ELEMENTS_CONFIGURATION_NAME = 'shadowRuntimeElements'

public static final String MODULE_INFO_CLASS = 'module-info.class'

private final ProjectConfigurationActionContainer configurationActionContainer
private final SoftwareComponentFactory softwareComponentFactory

@Inject
Expand Down Expand Up @@ -101,6 +104,20 @@ class ShadowJavaPlugin implements Plugin<Project> {
project.configurations.runtime,
]
shadow.exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class')
shadow.configurations = [project.configurations.findByName('runtimeClasspath') ?
project.configurations.runtimeClasspath : project.configurations.runtime]
/*
Remove excludes like this:
shadowJar {
...
allowModuleInfos()
}
*/
def excludes = ['META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA']
if (!shadow.isAllowModuleInfos()) {
excludes.add(MODULE_INFO_CLASS)
}
shadow.exclude(excludes)
}
project.artifacts.add(shadowConfiguration.name, taskProvider)
return taskProvider
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.jengelman.gradle.plugins.shadow.internal

import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import java.util.jar.JarFile

Expand All @@ -11,7 +12,7 @@ class RelocationUtil {
configuration.files.each { jar ->
JarFile jf = new JarFile(jar)
jf.entries().each { entry ->
if (entry.name.endsWith(".class") && entry.name != "module-info.class") {
if (entry.name.endsWith(".class") && entry.name != ShadowJavaPlugin.MODULE_INFO_CLASS) {
packages << entry.name[0..entry.name.lastIndexOf('/') - 1].replaceAll('/', '.')
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.jengelman.gradle.plugins.shadow.tasks

import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin
import com.github.jengelman.gradle.plugins.shadow.ShadowStats
import com.github.jengelman.gradle.plugins.shadow.impl.RelocatorRemapper
import com.github.jengelman.gradle.plugins.shadow.internal.UnusedTracker
Expand Down Expand Up @@ -41,6 +42,7 @@ import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.commons.ClassRemapper

import javax.annotation.Nonnull
import javax.annotation.Nullable
import java.util.zip.ZipException

Expand All @@ -59,11 +61,13 @@ class ShadowCopyAction implements CopyAction {
private final boolean preserveFileTimestamps
private final boolean minimizeJar
private final UnusedTracker unusedTracker
private final boolean allowModuleInfos

ShadowCopyAction(File zipFile, ZipCompressor compressor, DocumentationRegistry documentationRegistry,
String encoding, List<Transformer> transformers, List<Relocator> relocators,
PatternSet patternSet, ShadowStats stats,
boolean preserveFileTimestamps, boolean minimizeJar, UnusedTracker unusedTracker) {
boolean preserveFileTimestamps, boolean minimizeJar, UnusedTracker unusedTracker,
boolean allowModuleInfos) {

this.zipFile = zipFile
this.compressor = compressor
Expand All @@ -76,6 +80,7 @@ class ShadowCopyAction implements CopyAction {
this.preserveFileTimestamps = preserveFileTimestamps
this.minimizeJar = minimizeJar
this.unusedTracker = unusedTracker
this.allowModuleInfos = allowModuleInfos
}

@Override
Expand Down Expand Up @@ -201,7 +206,17 @@ class ShadowCopyAction implements CopyAction {
private final Set<String> unused
private final ShadowStats stats

private Map<String, Map> visitedFiles = new HashMap<>()
private class VisitedFileInfo {
long size
RelativePath originJar

VisitedFileInfo(long size, @Nonnull RelativePath originJar) {
this.size = size
this.originJar = originJar
}
}

private Map<String, VisitedFileInfo> visitedFiles = new HashMap<>()

StreamAction(ZipOutputStream zipOutStr, String encoding, List<Transformer> transformers,
List<Relocator> relocators, PatternSet patternSet, Set<String> unused,
Expand Down Expand Up @@ -235,7 +250,7 @@ class ShadowCopyAction implements CopyAction {
originJar = new RelativePath(false)
}

visitedFiles.put(path.toString(), [size: size, originJar: originJar])
visitedFiles.put(path.toString(), new VisitedFileInfo(size, originJar))
return true
}

Expand Down Expand Up @@ -312,37 +327,7 @@ class ShadowCopyAction implements CopyAction {
if (archiveFile.classFile || !isTransformable(archiveFile)) {
String path = archiveFilePath.toString()

if (path.endsWith("module-info.class")) {
log.warn("module-info collision")

def moduleFileName = "module-info"
def moduleFileSuffix = ".class"
File disassembleModFile = File.createTempFile(moduleFileName, moduleFileSuffix)

try (InputStream is = archive.getInputStream(archiveFilePath.entry)) {
try (OutputStream os = new FileOutputStream(disassembleModFile)) {
IOUtils.copyLarge(is, os)
}
}

ProcessBuilder processBuilder = new ProcessBuilder("javap", disassembleModFile.absolutePath)
processBuilder.redirectErrorStream(true)
Process process = processBuilder.start()
InputStream inputStream = process.getInputStream()
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
String line

while ((line = reader.readLine()) != null) {
log.warn(line)
}

int exitCode = process.waitFor()
if (exitCode != 0) {
log.warn("Process exited with code " + exitCode)
}

log.warn("module-info collision end")
}
listModuleInfoOnDemand(path, archive, archiveFilePath)

if (recordVisit(path, archiveFileSize, archiveFilePath) && !isUnused(archiveFilePath.entry.name)) {
if (!remapper.hasRelocators() || !archiveFile.classFile) {
Expand Down Expand Up @@ -382,6 +367,52 @@ class ShadowCopyAction implements CopyAction {
}
}

/**
Information about the 'module-info.class' if it isn't excluded. Including can be done with
<code>allowModuleInfos()</code>, like this:
<pre><code>
shadowJar {
...
allowModuleInfos()
}
</code></pre>
Based on the discussion in issue 710: <a href="https://github.com/GradleUp/shadow/issues/710">GitHub Issue #710</a>.
*/
private void listModuleInfoOnDemand(String path, ZipFile archive, RelativeArchivePath archiveFilePath) {
if (path.endsWith(ShadowJavaPlugin.MODULE_INFO_CLASS) && allowModuleInfos) {
log.warn("======== Warning: {}/{} contains module-info - Listing content ========",
RelativePath.parse(true, archive.name).lastName, path)

def moduleFileName = "module-info"
def moduleFileSuffix = ".class"
File disassembleModFile = File.createTempFile(moduleFileName, moduleFileSuffix)

try (InputStream is = archive.getInputStream(archiveFilePath.entry)) {
try (OutputStream os = new FileOutputStream(disassembleModFile)) {
IOUtils.copyLarge(is, os)
}
}

ProcessBuilder processBuilder = new ProcessBuilder("javap", disassembleModFile.absolutePath)
processBuilder.redirectErrorStream(true)
Process process = processBuilder.start()
InputStream inputStream = process.getInputStream()
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))

String line
while ((line = reader.readLine()) != null) {
log.warn(line)
}

int exitCode = process.waitFor()
if (exitCode != 0) {
log.warn("Process exited with code " + exitCode)
}

log.warn("======== module-info content listing end ========")
}
}

private void addParentDirectories(RelativeArchivePath file) {
if (file) {
addParentDirectories(file.parent)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.jengelman.gradle.plugins.shadow.tasks;

import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin;
import com.github.jengelman.gradle.plugins.shadow.ShadowStats;
import com.github.jengelman.gradle.plugins.shadow.internal.DefaultDependencyFilter;
import com.github.jengelman.gradle.plugins.shadow.internal.DependencyFilter;
Expand Down Expand Up @@ -72,6 +73,8 @@ public FileCollection call() {
}
});

private boolean isAllowModuleInfos;

public ShadowJar() {
super();
setDuplicatesStrategy(
Expand Down Expand Up @@ -142,7 +145,7 @@ protected CopyAction createCopyAction() {
getSourceSetsClassesDirs().getFiles(), getToMinimize()) : null;
return new ShadowCopyAction(getArchiveFile().get().getAsFile(), getInternalCompressor(), documentationRegistry,
this.getMetadataCharset(), transformers, relocators, getRootPatternSet(), shadowStats,
isPreserveFileTimestamps(), minimizeJar, unusedTracker);
isPreserveFileTimestamps(), minimizeJar, unusedTracker, isAllowModuleInfos);
}

@Classpath
Expand Down Expand Up @@ -263,6 +266,23 @@ public ShadowJar removeDefaultTransformers() {
return this;
}

/**
* Allows module-info.class's to be included in the final jar, and informs about the contents
* of the module-info.class files it finds.
*
* @return this
*/
public ShadowJar allowModuleInfos() {
this.isAllowModuleInfos = true;
getExcludes().remove(ShadowJavaPlugin.MODULE_INFO_CLASS);
return this;
}

@Internal
public boolean isAllowModuleInfos() {
return isAllowModuleInfos;
}

private boolean isCacheableTransform(Class<? extends Transformer> clazz) {
return clazz.isAnnotationPresent(CacheableTransformer.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ class ShadowPluginSpec extends PluginSpecification {
/* Shouldn't appear, because the default StandardFileTransformer should've merged it,
instead of just dropping all following licenses. */
assert !result.output.contains('license.txt')
// No module listing should appear. module-info.class is excluded by default.
assert !result.output.contains('open module org.example')
}
def 'Tests the removal of the default transformer'() {
Expand All @@ -167,7 +169,7 @@ class ShadowPluginSpec extends PluginSpecification {
|task shadow(type: ${ShadowJar.name}) {
| destinationDirectory = buildDir
| archiveBaseName = 'shadow'
| removeDefaultTransformers()
| removeDefaultTransformers()
| from('${artifact.path}')
| from('${project.path}')
|}
Expand All @@ -182,6 +184,32 @@ class ShadowPluginSpec extends PluginSpecification {
/\s+IGNORING test\.json from test-project-1\.0-SNAPSHOT.jar, size is different/
// Without the StandardFileTransformer there should be a warning about multiple license files with the same name.
assert result.output.contains('license.txt')
// No module listing should appear. module-info.class is excluded by default.
assert !result.output.contains('open module org.example')
}
def 'Tests the info about the module-info.class, if removed from standard excludes'() {
given:
URL artifact = this.class.classLoader.getResource('test-artifact-1.0-SNAPSHOT.jar')
URL project = this.class.classLoader.getResource('test-project-1.0-SNAPSHOT.jar')
buildFile << """
|task shadow(type: ${ShadowJar.name}) {
| destinationDirectory = buildDir
| archiveBaseName = 'shadow'
| allowModuleInfos()
| from('${artifact.path}')
| from('${project.path}')
|}
""".stripMargin()
when:
BuildResult result = run('shadow')
then:
assert result.output.contains('module-info.class')
// Because allowModuleInfos() is used, the module listing should appear.
assert result.output.contains('open module org.example')
}
def 'include project sources'() {
Expand Down
Binary file modified src/test/resources/test-artifact-1.0-SNAPSHOT.jar
Binary file not shown.
Binary file modified src/test/resources/test-project-1.0-SNAPSHOT.jar
Binary file not shown.

0 comments on commit 3ed4f3a

Please sign in to comment.