2008-09-17 11 views
20

Ich habe viel zu viel Zeit damit verbracht, dies herauszufinden. Dies sollte die einfachste Sache sein und jeder, der Java-Anwendungen in Gläsern verteilt, muss damit umgehen.Wie erstellen Sie eine MANIFEST.MF, die verfügbar ist, wenn Sie ein in der Produktion befindliches JAR testen und ausführen?

Ich möchte nur wissen, wie ich meiner Java-App Versionierung hinzufügen kann, damit ich beim Testen auf die Versionsinformationen zugreifen kann, z. Debugging in Eclipse und läuft aus einem Glas.

Hier ist, was ich in meinem build.xml haben:

<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> 

Diese /META-INF/MANIFEST.MF schafft und ich kann die Werte lesen, wenn ich in Eclipse bin Debuggen 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."); 
    }   

}

Aber, wenn ich die jAR-Datei erstellen, lädt er das Manifest eines anderen Glas (vermutlich das erste Glas durch die Anwendung geladen - in meinem Fall activation.jar).

Auch der folgende Code funktioniert nicht entweder, obwohl alle die richtigen Werte sind in der Manifest-Datei.

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

Irgendwelche Ideen?

Antwort

0

Verwenden Sie einfach nicht das Manifest. Erstellen Sie eine foo.properties.original Datei mit einem Gehalt wie version = @ VERSION @

Und in ther gleiche Aufgabe, die Sie Jaring Sie eine Kopie tun können, um Copu foo.properties.original und dann

0

Ich werde normalerweise auch eine Versionsdatei verwenden. Ich werde eine Datei pro Glas erstellen, da jedes Glas seine eigene Version haben kann.

1

Sie können das Manifest zugreifen (oder andere) Datei in einem Glas, wenn Sie den gleichen Klassenlader zu verwenden, wie verwendet wurde, um die Klassen zu laden.

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

Wenn Sie Multithread-Anwendung sind die folgenden:

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

Dies ist auch eine wirklich nützliche Technik für eine Standard-Konfigurationsdatei im Glas einschließlich.

2

Sie wollen diese verwenden:

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

Sie können die URL analysieren um herauszufinden, welche jar das Manifest, wenn aus und dann die URL über getInputStream() lesen das Manifest zu analysieren.

1

Hier ist, was ich gefunden habe, das funktioniert:

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();   
    } 

} 

Hier ist der passende 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> 
+0

In einer realen Anwendung, die alle Bibliotheken von Drittanbietern verwendet, ist dies sehr wahrscheinlich die falschen zurückzukehren Manifest. –

+0

Klassenname sollte in Großbuchstaben beginnen. Es sollte PackageVersion anstelle von packageVersion sein. – Carlos

1

ClassLoader.getResource(String) wird das erste Manifest laden Sie es auf dem Classpath findet, die das Manifest für eine andere JAR-Datei sein kann. Daher können Sie entweder enumerate all the manifests den gewünschten suchen oder einen anderen Mechanismus verwenden, z. B. eine Eigenschaftendatei mit einem eindeutigen Namen.

+0

Sie haben Recht, dass getResource() oft das falsche Manifest findet; Ich erlebe das; Ich habe Ihren Link zu ClassLoader.getResources() versucht, alle Manifeste aufzulisten, aber getResources ("/ META-INF/MANIFEST.MF") gibt nichts zurück, und getResources ("") gibt mehr als nur nützliches zurück. Ich vermute, ich benutze den falschen Klassenlader, aber dann "alle Manifeste auflisten" geht an "alle ClassLoader aufzählen"! – metamatt

1

Ich habe den Kommentar von McDowell gefunden, um wahr zu sein - welche MANIFEST.MF-Datei abgeholt wird, hängt vom Klassenpfad ab und ist möglicherweise nicht der gewünschte. Ich benutze diese

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()); 

, die ich von link text

+0

Das funktioniert fast für mich, obwohl der Link zu dem, wo du das hast, jetzt gebrochen ist. Ich sage "fast", weil Class.getPackage() einen durch Punkte getrennten Namen (org.foo.bar) zurückgibt und Class.getSimpleName() einen durch Slash getrennten Namen (org/foo/bar) zurückgibt. Aus diesem Grund mag ich die Antwort von gibbss, die vermeidet, die Klassen-URL zu analysieren. – metamatt

10

angepasst Sie das Manifest für eine beliebige Klasse in einem beliebigen Gefäß ohne Parsen der Klasse URL bekommen (die spröde sein könnte). Suchen Sie einfach nach einer Ressource, von der Sie wissen, dass sie sich im gewünschten Jar befindet, und wandeln Sie anschließend die Verbindung mit JarURLConnection um.

Wenn der Code funktionieren soll, wenn die Klasse nicht in einem Jar gebündelt ist, fügen Sie eine instanceof check für den Typ der zurückgegebenen URL-Verbindung hinzu. Klassen in einer nicht entpackten Klassenhierarchie geben eine interne Sun FileURLConnection statt der JarUrlConnection zurück. Dann können Sie das Manifest mit einer der in anderen Antworten beschriebenen InputStream-Methoden laden.

@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); 
    } 
} 
+0

Schön, danke. Ich habe hier alles versucht, und von den (derzeit 3) Antworten, die anerkennen, dass Class.getResource() oft das falsche Manifest findet (im falschen Jar) und eine Lösung anbietet, um das richtige Manifest zu finden, mag ich dieses am besten. – metamatt

+0

Wenn Sie etwas Bestimmtes wollen, verwenden Sie 'atts.getValue (" Attribut-Name ")' oder 'atts.get (new Attributes.Name (" Attribut-Name "))', nicht-generischer Code FTW! – TWiStErRob