Как вы создаете файл MANIFEST.MF, который доступен, когда вы тестируете и запускаете из jar-файла? - PullRequest
20 голосов
/ 17 сентября 2008

Я потратил слишком много времени, пытаясь понять это. Это должно быть самым простым делом, и каждый, кто распространяет Java-приложения в jar-файлах, должен иметь с этим дело.

Я просто хочу знать, как правильно добавить версии в мое приложение Java, чтобы я мог получить доступ к информации о версии во время тестирования, например, отладка в Eclipse и , работающем с банки.

Вот что у меня есть в моем 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, загруженный приложением - в моем случае, activ.jar).

Кроме того, следующий код также не работает, хотя все правильные значения находятся в файле манифеста.

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

Есть идеи?

Ответы [ 9 ]

10 голосов
/ 18 июня 2010

Вы можете получить манифест для произвольного класса в произвольном фляге, не анализируя url-класс (который может быть хрупким) Просто найдите ресурс, который, как вы знаете, находится в желаемом фляге, и приведите соединение к JarURLConnection.

Если вы хотите, чтобы код работал, когда класс не связан в банке, добавьте экземпляр проверки типа возвращаемого соединения URL. Классы в неупакованной иерархии классов будут возвращать внутреннее 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);
    }
}
2 голосов
/ 18 сентября 2008

Вы хотите использовать это:

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

Вы можете проанализировать URL-адрес, чтобы выяснить, КАКОЙ jar манифеста, если от, а затем прочитать URL-адрес через getInputStream (), чтобы проанализировать манифест.

1 голос
/ 18 июня 2010

Я обнаружил, что комментарий Макдауэлла соответствует действительности - какой файл 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());

который я адаптировал из текст ссылки

1 голос
/ 05 марта 2009

ClassLoader.getResource (String) загрузит первый обнаруженный манифест в пути к классам, который может быть манифестом для некоторого другого файла JAR. Таким образом, вы можете перечислить все манифесты , чтобы найти тот, который вам нужен, или использовать какой-либо другой механизм, например файл свойств с уникальным именем.

1 голос
/ 29 сентября 2008

Вот что я нашел, что работает:

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>
1 голос
/ 17 сентября 2008

Вы можете получить доступ к файлу манифеста (или любого другого) в jar, если вы используете тот же загрузчик классов, что и для загрузки классов.

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

Если вы многопоточные, используйте следующее:

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

Это также очень полезный метод для включения файла конфигурации по умолчанию в jar.

0 голосов
/ 30 декабря 2012

Вы можете использовать служебный класс Manifests из jcabi-manifest , который автоматизирует поиск и анализ всех файлов MANIFEST.MF, доступных в classpath. Затем вы читаете любой атрибут с одной строкой:

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

0 голосов
/ 17 сентября 2008

Я также обычно буду использовать файл версии. Я создам один файл для каждой банки, поскольку каждая банка может иметь свою собственную версию.

0 голосов
/ 17 сентября 2008

Только не используйте манифест. Создайте файл foo.properties.original с таким содержимым, как версия = @ VERSION @

И в той же задаче, которую вы выполняете, вы можете сделать копию в copu foo.properties.original и затем

...