Skip to content

Commit

Permalink
Add a dedicated goal to the Jandex Maven plugin to [re]index a JAR
Browse files Browse the repository at this point in the history
  • Loading branch information
Ladicek committed May 12, 2022
1 parent b830029 commit 07437ae
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 4 deletions.
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The following Ant task can be used with either the `maven-antrun-plugin` or an A
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>3.0.0-SNAPSHOT</version>
<version>${version.jandex}</version>
<executions>
<execution>
<id>make-index</id>
Expand Down Expand Up @@ -100,7 +100,7 @@ If you need to process more than one directory of classes, you can specify multi
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>3.0.0-SNAPSHOT</version>
<version>${version.jandex}</version>
<executions>
<execution>
<id>make-index</id>
Expand Down Expand Up @@ -168,6 +168,55 @@ A `groupId` and `artifactId` are mandatory, a `classifier` is optional:
</fileSet>
```

### Usage with Shading

The Jandex Maven plugin has an additional goal `jandex-jar` that can be used to create an index inside an existing JAR.
This goal is not bound to any phase by default, so you have to configure that manually.

It is useful together with shading, where the Maven Shade plugin creates a JAR from multiple previously existing JARs.
A shaded JAR may already contain a Jandex index, if at least one of the constituent JARs contains one, but that index is most likely _not_ what you want.
First, it is an unmodified index originating in one of the constituent JARs.
If multiple constituent JARs contain an index, only one of them makes it to the shaded JAR; the others are lost.
Second, during shading, classes may be relocated, so the index data may become stale.

There is no support for merging existing index files from the original JARs.
Likewise, there is no support for applying class relocations over an existing index.

In short, if you want a shaded JAR to have a Jandex index, you have to reindex it.

```xml
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>${version.jandex}</version>
<executions>
<execution>
<id>uberjar-index</id>
<phase>package</phase>
<goals>
<goal>jandex-jar</goal>
</goals>
<configuration>
<jar>${project.build.directory}/${project.build.finalName}.jar</jar>
<includes>
<include>com/example/my/project/**/*.class</include>
</includes>
<excludes>
<exclude>com/example/**/_private/*.class</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
```

If we want to reindex a shaded JAR, we have to make sure that the `jandex-jar` goal of the Jandex Maven plugin executes _later_ than the `shade` goal of the Maven Shade plugin.
In this example, we bind the `jandex-jar` goal to the `package` phase, which is also the phase in which the `shade` goal of the Maven Shade plugin executes by default.
In such case, we have to put the `<plugin>` element of the Jandex Maven plugin _after_ the `<plugin>` element of the Maven Shade plugin.

Remember that if the Jandex Maven plugin operates on a JAR that was produced by the Maven Shade plugin, and if the Maven Shade plugin is configured to perform class relocations, the Jandex Maven plugin operates on already relocated classes.
This is important for configuring includes and excludes correctly.

## Adding the Jandex API to your Maven project

Just add the following to your POM:
Expand Down
73 changes: 73 additions & 0 deletions maven-plugin/src/it/jar/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>

<parent>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-build-parent</artifactId>
<version>31</version>
</parent>

<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin-jar</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jandex</artifactId>
<version>2.4.0.Final</version>
</dependency>
<dependency>
<!-- has META-INF/jandex.idx -->
<groupId>io.smallrye.common</groupId>
<artifactId>smallrye-common-annotation</artifactId>
<version>1.11.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>uberjar</id>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<id>uberjar-index</id>
<phase>package</phase>
<goals>
<goal>jandex-jar</goal>
</goals>
<configuration>
<jar>${project.build.directory}/${project.build.finalName}.jar</jar>
<includes>
<include>org/jboss/jandex/maven/**/*.class</include>
<include>org/jboss/jandex/MethodParameter*.class</include>
<include>io/smallrye/common/annotation/Experimental.class</include>
</includes>
<excludes>
<exclude>org/jboss/jandex/MethodParameterTypeTarget.class</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.jboss.jandex.maven.jar;

public class SomeClass {
}
17 changes: 17 additions & 0 deletions maven-plugin/src/it/jar/verify.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import java.util.zip.ZipFile
import org.jboss.jandex.IndexReader

def jarFile = new File(basedir, 'target/jandex-maven-plugin-jar-1.0-SNAPSHOT.jar')
assert jarFile.exists() : "File ${jarFile} does not exist"
assert jarFile.length() > 0 : "File ${jarFile} is empty"

def jar = new ZipFile(jarFile)
def indexEntry = jar.getEntry("META-INF/jandex.idx")
assert indexEntry != null : "JAR ${jarFile} doesn't contain an index"

def index = new IndexReader(jar.getInputStream(indexEntry)).read()
assert index.getKnownClasses().size() == 3 : "Index in ${jarFile} does not contain exactly 3 classes"

assert index.getClassByName("org.jboss.jandex.maven.jar.SomeClass") != null
assert index.getClassByName("org.jboss.jandex.MethodParameterInfo") != null
assert index.getClassByName("io.smallrye.common.annotation.Experimental") != null
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public class JandexGoal extends AbstractMojo {
private boolean processDefaultFileSet;

/**
* Print verbose output (debug output without needing to enable -X for the whole build)
* Print verbose output (debug output without needing to enable -X for the whole build).
*/
@Parameter(defaultValue = "false")
private boolean verbose;
Expand All @@ -116,7 +116,7 @@ public class JandexGoal extends AbstractMojo {
* Skip execution if set.
*/
@Parameter(property = "jandex.skip", defaultValue = "false")
private boolean skip = true;
private boolean skip;

public void execute() throws MojoExecutionException {
if (skip) {
Expand Down
181 changes: 181 additions & 0 deletions maven-plugin/src/main/java/org/jboss/jandex/maven/JandexJarGoal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package org.jboss.jandex.maven;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.jboss.jandex.ClassSummary;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexWriter;
import org.jboss.jandex.Indexer;

/**
* Generate a Jandex index inside a given JAR.
*/
@Mojo(name = "jandex-jar", threadSafe = true)
public class JandexJarGoal extends AbstractMojo {
/**
* The JAR that should be indexed and inside which the index should be stored.
*/
@Parameter(required = true)
private File jar;

/**
* Path to the index inside the JAR. Defaults to <code>META-INF/jandex.idx</code>.
*/
@Parameter(defaultValue = "META-INF/jandex.idx")
private String indexName;

/**
* Names or glob patterns of files in the JAR that should be indexed.
*/
@Parameter
private List<String> includes;

/**
* Names or glob patterns of files in the JAR that should <em>not</em> be indexed.
* Excludes have priority over includes.
*/
@Parameter
private List<String> excludes;

@Parameter(defaultValue = "true")
private boolean useDefaultExcludes;

/**
* Print verbose output (debug output without needing to enable -X for the whole build).
*/
@Parameter(defaultValue = "false")
private boolean verbose;

/**
* Skip execution if set.
*/
@Parameter(property = "jandex.skip", defaultValue = "false")
private boolean skip;

public void execute() throws MojoExecutionException {
if (skip) {
getLog().info("Jandex execution skipped");
return;
}

if (!jar.isFile()) {
getLog().warn("Skipping, expected JAR does not exist or is not a file: " + jar);
return;
}

Index index = indexJar();

getLog().info("Saving Jandex index into JAR: " + jar);
Path tmp = createTempFile("jandextmp");
try (ZipFile zip = new ZipFile(jar);
ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(tmp.toFile().toPath()))) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.isDirectory() || entry.getName().equals(indexName)) {
continue;
}

ZipEntry newEntry = new ZipEntry(entry);
// Compression level and format can vary across implementations
if (newEntry.getMethod() != ZipEntry.STORED) {
newEntry.setCompressedSize(-1);
}
out.putNextEntry(newEntry);
try (InputStream in = zip.getInputStream(entry)) {
copy(in, out);
}
}

out.putNextEntry(new ZipEntry(indexName));
new IndexWriter(out).write(index);
} catch (IOException e) {
try {
Files.deleteIfExists(tmp);
} catch (IOException e1) {
e.addSuppressed(e1);
}
throw new MojoExecutionException(e.getMessage(), e);
}

Path originalJar = jar.toPath();
Path backupJar = createTempFile("jandexbackup");

try {
Files.move(originalJar, backupJar, StandardCopyOption.REPLACE_EXISTING);
Files.move(tmp, originalJar);
Files.delete(backupJar);
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}

private Index indexJar() throws MojoExecutionException {
ArchiveScanner scanner = new ArchiveScanner(jar);
scanner.setFilenameComparator(String::compareTo);
if (useDefaultExcludes) {
scanner.addDefaultExcludes();
}
if (includes != null) {
scanner.setIncludes(includes.toArray(new String[0]));
}
if (excludes != null) {
scanner.setExcludes(excludes.toArray(new String[0]));
}
scanner.scan();
String[] filesInJar = scanner.getIncludedFiles();

Indexer indexer = new Indexer();
try (ZipFile zip = new ZipFile(jar)) {
for (String file : filesInJar) {
if (file.endsWith(".class")) {
try (InputStream in = zip.getInputStream(zip.getEntry(file))) {
ClassSummary info = indexer.indexWithSummary(in);
if (isVerbose() && info != null) {
getLog().info("Indexed " + info.name() + " (" + info.annotationsCount() + " annotations)");
}
}
}
}
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
return indexer.complete();
}

private Path createTempFile(String suffix) throws MojoExecutionException {
try {
return Files.createTempFile(jar.toPath().getParent(), jar.getName(), suffix);
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}

private static void copy(InputStream in, OutputStream out) throws IOException {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.flush();
}

private boolean isVerbose() {
return verbose || getLog().isDebugEnabled();
}
}

0 comments on commit 07437ae

Please sign in to comment.