Steve Cohen
Steve Cohen

Reputation: 4859

Reading My Own Jar's Manifest Take 2

This is in response to the near-universally accepted answer to an old question. Since the answer is rather old (2009) and since it isn't working for me, I thought I'd ask to see if anyone knows why.

My use case is developing a gradle plugin for internal use. Whenever the plugin is applied, I'd dearly like to be able to print what version of my plugin is being used. So I write the version into the plugin's manifest when the plugin is being built and attempt to read it back from the manifest when the plugin is applied, using the technique in the linked answer.

I wrote up some code along the lines of the answer but it's not working. I added a bunch of debugging code, and I can see that the code first finds the Manifest provided by the JDK itself:

Manifest-Version: 1.0
Created-By: 1.7.0_07 (Oracle Corporation)

Then it finds my manifest, the one I am looking for, but throws a FileNotFoundException:

java.io.FileNotFoundException: JAR entry META-INF/MANIFEST.MF not found in {path to my plugin jar in my local maven repository}. How could

        Enumeration<URL> resources = getClass().getClassLoader()
                .getResources("META-INF/MANIFEST.MF");

find the manifest and then throw a FileNotFoundException when trying to read it with:

Manifest manifest = new Manifest(resources.nextElement().openStream());

This is the line where the exception is thrown.

Can someone explain this odd behavior or come up with another, perhaps newer way of reading the manifest?

This is running under Eclipse, in Windows, by the way. Haven't tried it yet on Linux, where it might actually work, but I'd like it to work in both cases.

Upvotes: 2

Views: 1630

Answers (2)

Steve Cohen
Steve Cohen

Reputation: 4859

Rather than trying to read manifest, with the weird IO issues (which I'd still like to understand), we can take advantage of what the Java Class Loader already provides in the java.lang.Package. There is the private Package(String name, Manifest man, URL url, ClassLoader loader) constructor which is invoked when a package is loaded from a jar. It reads the Manifest's standard attributes and stores them in Package member variables:

private final String pkgName;
private final String specTitle;
private final String specVersion;
private final String specVendor;
private final String implTitle;
private final String implVersion;
private final String implVendor;

These manifest entries must have the proper keys e.g. "Implementation-Version", etc., which are found in java.util.jar.Attributes.Name

With the manifest built according to these standards, it is possible to get these values without doing another read. Hat tip to https://stackoverflow.com/a/23280647/811299

Upvotes: 1

lance-java
lance-java

Reputation: 28101

If it were me, I'd generate a java file at build time containing the version:

Eg: Put the following file in src/template/java/com/foo/MyPluginProperties.java

package com.foo;
public class MyPluginProperties {
   public static String getVersion() {
      return "@version@";
   }
}

Then in build.gradle

def generatedJava = file("$buildDir/generated/java")
task generateSource(type:Copy) {
    def tokens = [version: project.version]

    // configure task inputs for gradle's up-to-date checks
    inputs.property "tokens", tokens
    from "src/template/java"
    filter(ReplaceTokens, tokens: tokens)
    into generatedJava
}

// wire the task into the gradle DAG
compileJava.dependsOn generateSource

// add the generated directory to the main source set so it's compiled
sourceSets.main.java {
   srcDir generatedJava
}

Then, in your plugin you could invoke the following:

com.foo.MyPluginProperties.getVersion()

Upvotes: 2

Related Questions