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.
我花了太多时间试图解决这个问题。这应该是最简单的事情,每个在Java中分发Java应用程序的人都必须处理它。
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.
我只是想知道向我的Java应用程序添加版本控制的正确方法,以便我可以在测试时访问版本信息,例如在Eclipse中调试并从jar运行。
Here's what I have in my build.xml:
这是我在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:
这创建了/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.");
}
}
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).
但是,当我创建jar文件时,它会加载另一个jar的清单(可能是应用程序加载的第一个jar - 在我的例子中,是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?
9 个解决方案
#1
10
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.
您可以在任意jar中获取任意类的清单,而无需解析类URL(可能很脆弱)。只需找到您知道的jar所需的资源,然后将连接转换为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.
如果您希望代码在类未捆绑在jar中时工作,请添加一个instanceof检查返回的URL连接类型。解压缩的类层次结构中的类将返回内部Sun FileURLConnection而不是JarUrlConnection。然后,您可以使用其他答案中描述的InputStream方法之一加载Manifest。
@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
2
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.
您可以解析URL以找出清单中的清单,然后通过getInputStream()读取清单以解析清单。
#3
1
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:
这是匹配的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>
#4
1
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.
如果使用与加载类相同的类加载器,则可以访问jar中的清单(或任何其他)文件。
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.
这也是在jar中包含默认配置文件的一种非常有用的技术。
#5
1
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.
ClassLoader.getResource(String)将加载它在类路径上找到的第一个清单,该清单可能是某些其他JAR文件的清单。因此,您可以枚举所有清单以查找所需清单或使用其他机制,例如具有唯一名称的属性文件。
#6
1
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
我发现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());
which I adapted from link text
我改编自链接文本
#7
0
Just don't use the manifest. Create a foo.properties.original file, with a content such as version=@VERSION@
只是不要使用清单。创建一个foo.properties.original文件,其内容如version = @ VERSION @
And in ther same task you are jaring you can do a copy to copu foo.properties.original and then
在同样的任务中,你正在盯着你可以复制到copu foo.properties.original然后
#8
0
I will also usually use a version file. I will create one file per jar since each jar could have its own version.
我通常也会使用版本文件。我将为每个jar创建一个文件,因为每个jar都可以有自己的版本。
#9
0
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:
您可以使用jcabi-manifests中的实用程序类清单自动查找和解析类路径中可用的所有MANIFEST.MF文件。然后,您使用单行读取任何属性:
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
另外,请查看:http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html
#1
10
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.
您可以在任意jar中获取任意类的清单,而无需解析类URL(可能很脆弱)。只需找到您知道的jar所需的资源,然后将连接转换为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.
如果您希望代码在类未捆绑在jar中时工作,请添加一个instanceof检查返回的URL连接类型。解压缩的类层次结构中的类将返回内部Sun FileURLConnection而不是JarUrlConnection。然后,您可以使用其他答案中描述的InputStream方法之一加载Manifest。
@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
2
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.
您可以解析URL以找出清单中的清单,然后通过getInputStream()读取清单以解析清单。
#3
1
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:
这是匹配的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>
#4
1
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.
如果使用与加载类相同的类加载器,则可以访问jar中的清单(或任何其他)文件。
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.
这也是在jar中包含默认配置文件的一种非常有用的技术。
#5
1
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.
ClassLoader.getResource(String)将加载它在类路径上找到的第一个清单,该清单可能是某些其他JAR文件的清单。因此,您可以枚举所有清单以查找所需清单或使用其他机制,例如具有唯一名称的属性文件。
#6
1
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
我发现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());
which I adapted from link text
我改编自链接文本
#7
0
Just don't use the manifest. Create a foo.properties.original file, with a content such as version=@VERSION@
只是不要使用清单。创建一个foo.properties.original文件,其内容如version = @ VERSION @
And in ther same task you are jaring you can do a copy to copu foo.properties.original and then
在同样的任务中,你正在盯着你可以复制到copu foo.properties.original然后
#8
0
I will also usually use a version file. I will create one file per jar since each jar could have its own version.
我通常也会使用版本文件。我将为每个jar创建一个文件,因为每个jar都可以有自己的版本。
#9
0
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:
您可以使用jcabi-manifests中的实用程序类清单自动查找和解析类路径中可用的所有MANIFEST.MF文件。然后,您使用单行读取任何属性:
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
另外,请查看:http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html