如何创建在生产中测试和运行 jar 时可用的 MANIFEST.MF?

发布于 2024-07-06 04:22:39 字数 2153 浏览 6 评论 0原文

我花了太多时间试图解决这个问题。 这应该是最简单的事情,每个在 jar 中分发 Java 应用程序的人都必须处理它。

我只是想知道向 Java 应用程序添加版本控制的正确方法,以便在测试时可以访问版本信息,例如在 Eclipse 中调试和从 jar 运行。

这是我的 build.xml 中的内容:

<target name="jar" depends = "compile">
    <property name="version.num" value="1.0.0"/>
    <buildnumber file="build.num"/>
    <tstamp>
        <format property="TODAY" pattern="yyyy-MM-dd HH:mm:ss" />
    </tstamp>

    <manifest file="${build}/META-INF/MANIFEST.MF">
        <attribute name="Built-By" value="${user.name}" />
        <attribute name="Built-Date" value="${TODAY}" />                   
        <attribute name="Implementation-Title" value="MyApp" />
        <attribute name="Implementation-Vendor" value="MyCompany" />                
        <attribute name="Implementation-Version" value="${version.num}-b${build.number}"/>                              
    </manifest>

    <jar destfile="${build}/myapp.jar" basedir="${build}" excludes="*.jar" />                   
</target>

这将创建 /META-INF/MANIFEST.MF,当我在 Eclipse 中调试时,我可以读取这些值:

public MyClass()
{
    try
    {                        
        InputStream stream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");
        Manifest manifest = new Manifest(stream);            

        Attributes attributes = manifest.getMainAttributes();

        String implementationTitle = attributes.getValue("Implementation-Title");
        String implementationVersion = attributes.getValue("Implementation-Version");
        String builtDate = attributes.getValue("Built-Date");
        String builtBy = attributes.getValue("Built-By");
   }
   catch (IOException e)
   {            
        logger.error("Couldn't read manifest.");
   }        

}

但是,当我创建 jar 文件时,它会加载以下清单另一个 jar(可能是应用程序加载的第一个 jar - 在我的例子中是activation.jar)。

此外,尽管所有正确的值都在清单文件中,以下代码也不起作用。

    Package thisPackage = getClass().getPackage();
    String implementationVersion = thisPackage.getImplementationVersion();

有任何想法吗?

I've spent far too much time trying to figure this out. This should be the simplest thing and everyone who distributes Java applications in jars must have to deal with it.

I just want to know the proper way to add versioning to my Java app so that I can access the version information when I'm testing, e.g. debugging in Eclipse and running from a jar.

Here's what I have in my build.xml:

<target name="jar" depends = "compile">
    <property name="version.num" value="1.0.0"/>
    <buildnumber file="build.num"/>
    <tstamp>
        <format property="TODAY" pattern="yyyy-MM-dd HH:mm:ss" />
    </tstamp>

    <manifest file="${build}/META-INF/MANIFEST.MF">
        <attribute name="Built-By" value="${user.name}" />
        <attribute name="Built-Date" value="${TODAY}" />                   
        <attribute name="Implementation-Title" value="MyApp" />
        <attribute name="Implementation-Vendor" value="MyCompany" />                
        <attribute name="Implementation-Version" value="${version.num}-b${build.number}"/>                              
    </manifest>

    <jar destfile="${build}/myapp.jar" basedir="${build}" excludes="*.jar" />                   
</target>

This creates /META-INF/MANIFEST.MF and I can read the values when I'm debugging in Eclipse thusly:

public MyClass()
{
    try
    {                        
        InputStream stream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");
        Manifest manifest = new Manifest(stream);            

        Attributes attributes = manifest.getMainAttributes();

        String implementationTitle = attributes.getValue("Implementation-Title");
        String implementationVersion = attributes.getValue("Implementation-Version");
        String builtDate = attributes.getValue("Built-Date");
        String builtBy = attributes.getValue("Built-By");
   }
   catch (IOException e)
   {            
        logger.error("Couldn't read manifest.");
   }        

}

But, when I create the jar file, it loads the manifest of another jar (presumably the first jar loaded by the application - in my case, activation.jar).

Also, the following code doesn't work either although all the proper values are in the manifest file.

    Package thisPackage = getClass().getPackage();
    String implementationVersion = thisPackage.getImplementationVersion();

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

各自安好 2024-07-13 04:22:39

这是我发现有效的:

packageVersion.java:

package com.company.division.project.packageversion;

import java.io.IOException;
import java.io.InputStream;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

public class packageVersion
{
    void printVersion()
    {
        try
        {         
            InputStream stream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");

            if (stream == null)
            {
                System.out.println("Couldn't find manifest.");
                System.exit(0);
            }

            Manifest manifest = new Manifest(stream);

            Attributes attributes = manifest.getMainAttributes();

            String impTitle = attributes.getValue("Implementation-Title");
            String impVersion = attributes.getValue("Implementation-Version");
            String impBuildDate = attributes.getValue("Built-Date");
            String impBuiltBy = attributes.getValue("Built-By");

            if (impTitle != null)
            {
                System.out.println("Implementation-Title:   " + impTitle);
            }            
            if (impVersion != null)
            {
                System.out.println("Implementation-Version: " + impVersion);
            }
            if (impBuildDate != null)
            {
                System.out.println("Built-Date: " + impBuildDate);
            }
            if (impBuiltBy != null)
            {
                System.out.println("Built-By:   " + impBuiltBy);
            }

            System.exit(0);
        }
        catch (IOException e)
        {            
            System.out.println("Couldn't read manifest.");
        }        
    }

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        packageVersion version = new packageVersion();
        version.printVersion();        
    }

}

这是匹配的 build.xml:

<project name="packageVersion" default="run" basedir=".">

    <property name="src" location="src"/>
    <property name="build" location="bin"/>
    <property name="dist" location="dist"/>

    <target name="init">
        <tstamp>
            <format property="TIMESTAMP" pattern="yyyy-MM-dd HH:mm:ss" />
        </tstamp>
        <mkdir dir="${build}"/>
        <mkdir dir="${build}/META-INF"/>
    </target>

    <target name="compile" depends="init">
        <javac debug="on" srcdir="${src}" destdir="${build}"/>
    </target>

    <target name="dist" depends = "compile">        
        <mkdir dir="${dist}"/>      
        <property name="version.num" value="1.0.0"/>
        <buildnumber file="build.num"/>
        <manifest file="${build}/META-INF/MANIFEST.MF">
            <attribute name="Built-By" value="${user.name}" />
            <attribute name="Built-Date" value="${TIMESTAMP}" />                                
            <attribute name="Implementation-Vendor" value="Company" />
            <attribute name="Implementation-Title" value="PackageVersion" />
            <attribute name="Implementation-Version" value="${version.num} (b${build.number})"/>
            <section name="com/company/division/project/packageversion">
                <attribute name="Sealed" value="false"/>
            </section>          
        </manifest>     
        <jar destfile="${dist}/packageversion-${version.num}.jar" basedir="${build}" manifest="${build}/META-INF/MANIFEST.MF"/>                 
    </target>

    <target name="clean">
        <delete dir="${build}"/>
        <delete dir="${dist}"/>
    </target>

    <target name="run" depends="dist">      
        <java classname="com.company.division.project.packageversion.packageVersion">
            <arg value="-h"/>
            <classpath>
                <pathelement location="${dist}/packageversion-${version.num}.jar"/>
                <pathelement path="${java.class.path}"/>
            </classpath>
        </java>
    </target>

</project>

Here's what I've found that works:

packageVersion.java:

package com.company.division.project.packageversion;

import java.io.IOException;
import java.io.InputStream;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

public class packageVersion
{
    void printVersion()
    {
        try
        {         
            InputStream stream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");

            if (stream == null)
            {
                System.out.println("Couldn't find manifest.");
                System.exit(0);
            }

            Manifest manifest = new Manifest(stream);

            Attributes attributes = manifest.getMainAttributes();

            String impTitle = attributes.getValue("Implementation-Title");
            String impVersion = attributes.getValue("Implementation-Version");
            String impBuildDate = attributes.getValue("Built-Date");
            String impBuiltBy = attributes.getValue("Built-By");

            if (impTitle != null)
            {
                System.out.println("Implementation-Title:   " + impTitle);
            }            
            if (impVersion != null)
            {
                System.out.println("Implementation-Version: " + impVersion);
            }
            if (impBuildDate != null)
            {
                System.out.println("Built-Date: " + impBuildDate);
            }
            if (impBuiltBy != null)
            {
                System.out.println("Built-By:   " + impBuiltBy);
            }

            System.exit(0);
        }
        catch (IOException e)
        {            
            System.out.println("Couldn't read manifest.");
        }        
    }

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        packageVersion version = new packageVersion();
        version.printVersion();        
    }

}

Here's the matching build.xml:

<project name="packageVersion" default="run" basedir=".">

    <property name="src" location="src"/>
    <property name="build" location="bin"/>
    <property name="dist" location="dist"/>

    <target name="init">
        <tstamp>
            <format property="TIMESTAMP" pattern="yyyy-MM-dd HH:mm:ss" />
        </tstamp>
        <mkdir dir="${build}"/>
        <mkdir dir="${build}/META-INF"/>
    </target>

    <target name="compile" depends="init">
        <javac debug="on" srcdir="${src}" destdir="${build}"/>
    </target>

    <target name="dist" depends = "compile">        
        <mkdir dir="${dist}"/>      
        <property name="version.num" value="1.0.0"/>
        <buildnumber file="build.num"/>
        <manifest file="${build}/META-INF/MANIFEST.MF">
            <attribute name="Built-By" value="${user.name}" />
            <attribute name="Built-Date" value="${TIMESTAMP}" />                                
            <attribute name="Implementation-Vendor" value="Company" />
            <attribute name="Implementation-Title" value="PackageVersion" />
            <attribute name="Implementation-Version" value="${version.num} (b${build.number})"/>
            <section name="com/company/division/project/packageversion">
                <attribute name="Sealed" value="false"/>
            </section>          
        </manifest>     
        <jar destfile="${dist}/packageversion-${version.num}.jar" basedir="${build}" manifest="${build}/META-INF/MANIFEST.MF"/>                 
    </target>

    <target name="clean">
        <delete dir="${build}"/>
        <delete dir="${dist}"/>
    </target>

    <target name="run" depends="dist">      
        <java classname="com.company.division.project.packageversion.packageVersion">
            <arg value="-h"/>
            <classpath>
                <pathelement location="${dist}/packageversion-${version.num}.jar"/>
                <pathelement path="${java.class.path}"/>
            </classpath>
        </java>
    </target>

</project>
纸伞微斜 2024-07-13 04:22:39

您可以获取任意 jar 中任意类的清单,而无需解析类 url(这可能很脆弱)。 只需找到您知道的资源位于所需的 jar 中,然后将连接强制转换为 JarURLConnection。

如果您希望代码在类未捆绑在 jar 中时正常工作,请添加对返回的 URL 连接类型的 instanceof 检查。 解压的类层次结构中的类将返回内部 Sun FileURLConnection,而不是 JarUrlConnection。 然后,您可以使用其他答案中描述的 InputStream 方法之一加载清单。

@Test
public void testManifest() throws IOException {
    URL res = org.junit.Assert.class.getResource(org.junit.Assert.class.getSimpleName() + ".class");
    JarURLConnection conn = (JarURLConnection) res.openConnection();
    Manifest mf = conn.getManifest();
    Attributes atts = mf.getMainAttributes();
    for (Object v : atts.values()) {
        System.out.println(v);
    }
}

You can get the manifest for an arbitrary class in an arbitrary jar without parsing the class url (which could be brittle). Just locate a resource that you know is in the jar you want, and then cast the connection to JarURLConnection.

If you want the code to work when the class is not bundled in a jar, add an instanceof check on the type of URL connection returned. Classes in an unpacked class hierarchy will return a internal Sun FileURLConnection instead of the JarUrlConnection. Then you can load the Manifest using one of the InputStream methods described in other answers.

@Test
public void testManifest() throws IOException {
    URL res = org.junit.Assert.class.getResource(org.junit.Assert.class.getSimpleName() + ".class");
    JarURLConnection conn = (JarURLConnection) res.openConnection();
    Manifest mf = conn.getManifest();
    Attributes atts = mf.getMainAttributes();
    for (Object v : atts.values()) {
        System.out.println(v);
    }
}
傲鸠 2024-07-13 04:22:39

ClassLoader.getResource (String) 将加载在类路径中找到的第一个清单,该清单可能是其他 JAR 文件的清单。 因此,您可以 枚举所有清单以查找您想要的清单或使用某种其他机制,例如具有唯一名称的属性文件。

ClassLoader.getResource(String) will load the first manifest it finds on the classpath, which may be the manifest for some other JAR file. Thus, you can either enumerate all the manifests to find the one you want or use some other mechanism, such as a properties file with a unique name.

梦醒灬来后我 2024-07-13 04:22:39

您想使用它:

Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");

您可以解析 URL 以找出清单来自哪个 jar,然后通过 getInputStream() 读取 URL 来解析清单。

You want to use this:

Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");

You can parse the URL to figure out WHICH jar the manifest if from and then read the URL via getInputStream() to parse the manifest.

夏夜暖风 2024-07-13 04:22:39

我发现 McDowell 的评论是正确的 - 选择哪个 MANIFEST.MF 文件取决于类路径,并且可能不是想要的文件。 我使用这个

String cp = PCAS.class.getResource(PCAS.class.getSimpleName() + ".class").toString();
cp = cp.substring(0, cp.indexOf(PCAS.class.getPackage().getName())) 
              +  "META-INF/MANIFEST.MF";
Manifest mf = new Manifest((new URL(cp)).openStream());

改编自 链接文本

I've found the comment by McDowell to be true - which MANIFEST.MF file gets picked up depends on the classpath and might not be the one wanted. I use this

String cp = PCAS.class.getResource(PCAS.class.getSimpleName() + ".class").toString();
cp = cp.substring(0, cp.indexOf(PCAS.class.getPackage().getName())) 
              +  "META-INF/MANIFEST.MF";
Manifest mf = new Manifest((new URL(cp)).openStream());

which I adapted from link text

影子的影子 2024-07-13 04:22:39

如果您使用与加载类相同的类加载器,则可以访问 jar 中的清单(或任何其他)文件。

this.getClass().getClassLoader().getResourceAsStream( ... ) ;

如果您是多线程的,请使用以下命令:

Thread.currentThread().getContextClassLoader().getResourceAsStream( ... ) ;

这也是一种非常有用的技术,可以在 jar 中包含默认配置文件。

You can access the manifest (or any other) file within a jar if you use the same class loader to as was used to load the classes.

this.getClass().getClassLoader().getResourceAsStream( ... ) ;

If you are multi-threaded use the following:

Thread.currentThread().getContextClassLoader().getResourceAsStream( ... ) ;

This is also a realy useful technique for including a default configuration file within the jar.

撩动你心 2024-07-13 04:22:39

您可以使用实用程序类 Manifests 来自 jcabi-manifests,它可以自动查找和解析所有 MANIFEST。 MF 文件在类路径中可用。 然后,您可以用一行读取任何属性:

final String name = Manifests.read("Build-By");
final String date = Manifests.read("Build-Date");

另外,请检查一下:http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

You can use a utility class Manifests from jcabi-manifests that automates finding and parsing of all MANIFEST.MF files available in classpath. Then, you read any attribute with a one liner:

final String name = Manifests.read("Build-By");
final String date = Manifests.read("Build-Date");

Also, check this out: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

痞味浪人 2024-07-13 04:22:39

只是不要使用清单。 创建一个 foo.properties.original 文件,内容如下
version=@VERSION@

在您正在执行的同一任务中,您可以复制到 copu foo.properties.original,然后

Just don't use the manifest. Create a foo.properties.original file, with a content such as
version=@VERSION@

And in ther same task you are jaring you can do a copy to copu foo.properties.original and then

海拔太高太耀眼 2024-07-13 04:22:39

我通常也会使用版本文件。 我将为每个 jar 创建一个文件,因为每个 jar 都可以有自己的版本。

I will also usually use a version file. I will create one file per jar since each jar could have its own version.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文