Skip to content

Commit d19c391

Browse files
author
Jan Diederich
committed
Adding option allowModuleInfos() to make it optional to include module-info.class files easily
1 parent db94a49 commit d19c391

File tree

7 files changed

+131
-38
lines changed

7 files changed

+131
-38
lines changed

src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class ShadowJavaPlugin implements Plugin<Project> {
1919
public static final String SHADOW_JAR_TASK_NAME = 'shadowJar'
2020
public static final String SHADOW_GROUP = 'Shadow'
2121

22+
public static final String MODULE_INFO_CLASS = 'module-info.class'
23+
2224
private final ProjectConfigurationActionContainer configurationActionContainer
2325

2426
@Inject
@@ -89,7 +91,18 @@ class ShadowJavaPlugin implements Plugin<Project> {
8991
shadow.from(sourceSets.main.output)
9092
shadow.configurations = [project.configurations.findByName('runtimeClasspath') ?
9193
project.configurations.runtimeClasspath : project.configurations.runtime]
92-
shadow.exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class')
94+
/*
95+
Remove excludes like this:
96+
shadowJar {
97+
...
98+
allowModuleInfos()
99+
}
100+
*/
101+
def excludes = ['META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA']
102+
if (!shadow.isAllowModuleInfos()) {
103+
excludes.add(MODULE_INFO_CLASS)
104+
}
105+
shadow.exclude(excludes)
93106
}
94107
project.artifacts.add(ShadowBasePlugin.CONFIGURATION_NAME, project.tasks.named(SHADOW_JAR_TASK_NAME))
95108
}

src/main/groovy/com/github/jengelman/gradle/plugins/shadow/internal/RelocationUtil.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.jengelman.gradle.plugins.shadow.internal
22

3+
import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin
34
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
45
import java.util.jar.JarFile
56

@@ -11,7 +12,7 @@ class RelocationUtil {
1112
configuration.files.each { jar ->
1213
JarFile jf = new JarFile(jar)
1314
jf.entries().each { entry ->
14-
if (entry.name.endsWith(".class") && entry.name != "module-info.class") {
15+
if (entry.name.endsWith(".class") && entry.name != ShadowJavaPlugin.MODULE_INFO_CLASS) {
1516
packages << entry.name[0..entry.name.lastIndexOf('/') - 1].replaceAll('/', '.')
1617
}
1718
}

src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.groovy

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.jengelman.gradle.plugins.shadow.tasks
22

3+
import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin
34
import com.github.jengelman.gradle.plugins.shadow.ShadowStats
45
import com.github.jengelman.gradle.plugins.shadow.impl.RelocatorRemapper
56
import com.github.jengelman.gradle.plugins.shadow.internal.UnusedTracker
@@ -41,6 +42,7 @@ import org.objectweb.asm.ClassVisitor
4142
import org.objectweb.asm.ClassWriter
4243
import org.objectweb.asm.commons.ClassRemapper
4344

45+
import javax.annotation.Nonnull
4446
import javax.annotation.Nullable
4547
import java.util.zip.ZipException
4648

@@ -59,11 +61,13 @@ class ShadowCopyAction implements CopyAction {
5961
private final boolean preserveFileTimestamps
6062
private final boolean minimizeJar
6163
private final UnusedTracker unusedTracker
64+
private final boolean allowModuleInfos
6265

6366
ShadowCopyAction(File zipFile, ZipCompressor compressor, DocumentationRegistry documentationRegistry,
6467
String encoding, List<Transformer> transformers, List<Relocator> relocators,
6568
PatternSet patternSet, ShadowStats stats,
66-
boolean preserveFileTimestamps, boolean minimizeJar, UnusedTracker unusedTracker) {
69+
boolean preserveFileTimestamps, boolean minimizeJar, UnusedTracker unusedTracker,
70+
boolean allowModuleInfos) {
6771

6872
this.zipFile = zipFile
6973
this.compressor = compressor
@@ -76,6 +80,7 @@ class ShadowCopyAction implements CopyAction {
7680
this.preserveFileTimestamps = preserveFileTimestamps
7781
this.minimizeJar = minimizeJar
7882
this.unusedTracker = unusedTracker
83+
this.allowModuleInfos = allowModuleInfos
7984
}
8085

8186
@Override
@@ -201,7 +206,17 @@ class ShadowCopyAction implements CopyAction {
201206
private final Set<String> unused
202207
private final ShadowStats stats
203208

204-
private Map<String, Map> visitedFiles = new HashMap<>()
209+
private class VisitedFileInfo {
210+
long size
211+
RelativePath originJar
212+
213+
VisitedFileInfo(long size, @Nonnull RelativePath originJar) {
214+
this.size = size
215+
this.originJar = originJar
216+
}
217+
}
218+
219+
private Map<String, VisitedFileInfo> visitedFiles = new HashMap<>()
205220

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

238-
visitedFiles.put(path.toString(), [size: size, originJar: originJar])
253+
visitedFiles.put(path.toString(), new VisitedFileInfo(size, originJar))
239254
return true
240255
}
241256

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

315-
if (path.endsWith("module-info.class")) {
316-
log.warn("module-info collision")
317-
318-
def moduleFileName = "module-info"
319-
def moduleFileSuffix = ".class"
320-
File disassembleModFile = File.createTempFile(moduleFileName, moduleFileSuffix)
321-
322-
try (InputStream is = archive.getInputStream(archiveFilePath.entry)) {
323-
try (OutputStream os = new FileOutputStream(disassembleModFile)) {
324-
IOUtils.copyLarge(is, os)
325-
}
326-
}
327-
328-
ProcessBuilder processBuilder = new ProcessBuilder("javap", disassembleModFile.absolutePath)
329-
processBuilder.redirectErrorStream(true)
330-
Process process = processBuilder.start()
331-
InputStream inputStream = process.getInputStream()
332-
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
333-
String line
334-
335-
while ((line = reader.readLine()) != null) {
336-
log.warn(line)
337-
}
338-
339-
int exitCode = process.waitFor()
340-
if (exitCode != 0) {
341-
log.warn("Process exited with code " + exitCode)
342-
}
343-
344-
log.warn("module-info collision end")
345-
}
330+
listModuleInfoOnDemand(path, archive, archiveFilePath)
346331

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

370+
/**
371+
Information about the 'module-info.class' if it isn't excluded. Including can be done with
372+
<code>allowModuleInfos()</code>, like this:
373+
<pre><code>
374+
shadowJar {
375+
...
376+
allowModuleInfos()
377+
}
378+
</code></pre>
379+
Based on the discussion in issue 710: <a href="https://github.com/GradleUp/shadow/issues/710">GitHub Issue #710</a>.
380+
*/
381+
private void listModuleInfoOnDemand(String path, ZipFile archive, RelativeArchivePath archiveFilePath) {
382+
if (path.endsWith(ShadowJavaPlugin.MODULE_INFO_CLASS) && allowModuleInfos) {
383+
log.warn("======== Warning: {}/{} contains module-info - Listing content ========",
384+
RelativePath.parse(true, archive.name).lastName, path)
385+
386+
def moduleFileName = "module-info"
387+
def moduleFileSuffix = ".class"
388+
File disassembleModFile = File.createTempFile(moduleFileName, moduleFileSuffix)
389+
390+
try (InputStream is = archive.getInputStream(archiveFilePath.entry)) {
391+
try (OutputStream os = new FileOutputStream(disassembleModFile)) {
392+
IOUtils.copyLarge(is, os)
393+
}
394+
}
395+
396+
ProcessBuilder processBuilder = new ProcessBuilder("javap", disassembleModFile.absolutePath)
397+
processBuilder.redirectErrorStream(true)
398+
Process process = processBuilder.start()
399+
InputStream inputStream = process.getInputStream()
400+
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
401+
402+
String line
403+
while ((line = reader.readLine()) != null) {
404+
log.warn(line)
405+
}
406+
407+
int exitCode = process.waitFor()
408+
if (exitCode != 0) {
409+
log.warn("Process exited with code " + exitCode)
410+
}
411+
412+
log.warn("======== module-info content listing end ========")
413+
}
414+
}
415+
385416
private void addParentDirectories(RelativeArchivePath file) {
386417
if (file) {
387418
addParentDirectories(file.parent)

src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.jengelman.gradle.plugins.shadow.tasks;
22

3+
import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin;
34
import com.github.jengelman.gradle.plugins.shadow.ShadowStats;
45
import com.github.jengelman.gradle.plugins.shadow.internal.DefaultDependencyFilter;
56
import com.github.jengelman.gradle.plugins.shadow.internal.DependencyFilter;
@@ -72,6 +73,8 @@ public FileCollection call() {
7273
}
7374
});
7475

76+
private boolean isAllowModuleInfos;
77+
7578
public ShadowJar() {
7679
super();
7780
setDuplicatesStrategy(
@@ -142,7 +145,7 @@ protected CopyAction createCopyAction() {
142145
getSourceSetsClassesDirs().getFiles(), getToMinimize()) : null;
143146
return new ShadowCopyAction(getArchiveFile().get().getAsFile(), getInternalCompressor(), documentationRegistry,
144147
this.getMetadataCharset(), transformers, relocators, getRootPatternSet(), shadowStats,
145-
isPreserveFileTimestamps(), minimizeJar, unusedTracker);
148+
isPreserveFileTimestamps(), minimizeJar, unusedTracker, isAllowModuleInfos);
146149
}
147150

148151
@Classpath
@@ -263,6 +266,23 @@ public ShadowJar removeDefaultTransformers() {
263266
return this;
264267
}
265268

269+
/**
270+
* Allows module-info.class's to be included in the final jar, and informs about the contents
271+
* of the module-info.class files it finds.
272+
*
273+
* @return this
274+
*/
275+
public ShadowJar allowModuleInfos() {
276+
this.isAllowModuleInfos = true;
277+
getExcludes().remove(ShadowJavaPlugin.MODULE_INFO_CLASS);
278+
return this;
279+
}
280+
281+
@Internal
282+
public boolean isAllowModuleInfos() {
283+
return isAllowModuleInfos;
284+
}
285+
266286
private boolean isCacheableTransform(Class<? extends Transformer> clazz) {
267287
return clazz.isAnnotationPresent(CacheableTransformer.class);
268288
}

src/test/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowPluginSpec.groovy

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ class ShadowPluginSpec extends PluginSpecification {
155155
/* Shouldn't appear, because the default StandardFileTransformer should've merged it,
156156
instead of just dropping all following licenses. */
157157
assert !result.output.contains('license.txt')
158+
// No module listing should appear. module-info.class is excluded by default.
159+
assert !result.output.contains('open module org.example')
158160
}
159161
160162
def 'Tests the removal of the default transformer'() {
@@ -166,7 +168,7 @@ class ShadowPluginSpec extends PluginSpecification {
166168
|task shadow(type: ${ShadowJar.name}) {
167169
| destinationDirectory = buildDir
168170
| archiveBaseName = 'shadow'
169-
| removeDefaultTransformers()
171+
| removeDefaultTransformers()
170172
| from('${artifact.path}')
171173
| from('${project.path}')
172174
|}
@@ -181,6 +183,32 @@ class ShadowPluginSpec extends PluginSpecification {
181183
/\s+IGNORING test\.json from test-project-1\.0-SNAPSHOT.jar, size is different/
182184
// Without the StandardFileTransformer there should be a warning about multiple license files with the same name.
183185
assert result.output.contains('license.txt')
186+
// No module listing should appear. module-info.class is excluded by default.
187+
assert !result.output.contains('open module org.example')
188+
}
189+
190+
def 'Tests the info about the module-info.class, if removed from standard excludes'() {
191+
given:
192+
URL artifact = this.class.classLoader.getResource('test-artifact-1.0-SNAPSHOT.jar')
193+
URL project = this.class.classLoader.getResource('test-project-1.0-SNAPSHOT.jar')
194+
195+
buildFile << """
196+
|task shadow(type: ${ShadowJar.name}) {
197+
| destinationDirectory = buildDir
198+
| archiveBaseName = 'shadow'
199+
| allowModuleInfos()
200+
| from('${artifact.path}')
201+
| from('${project.path}')
202+
|}
203+
""".stripMargin()
204+
205+
when:
206+
BuildResult result = run('shadow')
207+
208+
then:
209+
assert result.output.contains('module-info.class')
210+
// Because allowModuleInfos() is used, the module listing should appear.
211+
assert result.output.contains('open module org.example')
184212
}
185213
186214
def 'include project sources'() {
942 Bytes
Binary file not shown.
980 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)