You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Jar.java 18 kB

Addition of ZipFileset facilities. Descibed by the author --- With these patches, Zip (and derivative tasks such as Jar and War) can merge the entries of multiple zip files into a single output zip file. The contents of an input zip file may be selectively extracted based on include/exclude patterns. An included zip file is specified using a <fileset> with a "src" attribute, as in: <target name="jartest"> <jar jarfile="utils.jar"> <fileset src="weblogic.jar" includes="weblogic/utils/" excludes="weblogic/utils/jars/,**/reflect/" /> </jar> </target> In this example, a subset of the "weblogic/utils" directory is extracted from weblogic.jar, into utils.jar. The fileset may also contain "prefix" and "fullpath" attributes (the functionality of PrefixedFileSet has been retained in the new class ZipFileSet). Prefixes apply to directory-based and zip-based filesets. The fullpath attributes applies only to a single file in a directory-based fileset. The War task may extract entries from a zip file for all of its filesets (including the files in "classes" and "lib"). The motivation for this change is: 1) There is significant overlap between "jlink" and "zip", and it seemed better to combine them. 2) "jlink" does not support include/exclude patterns which are extremely useful for writing packaging-type tasks such as Zip/Jar/War. This was my main motivation. 3) By adding this functionality to the base task, it can also be used in derivative tasks such as Jar and War. --- Submitted By: Don Ferguson <don@bea.com> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268458 13f79535-47bb-0310-9956-ffa450edef68
25 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Ant", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.tools.ant.taskdefs;
  55. import org.apache.tools.ant.BuildException;
  56. import org.apache.tools.ant.FileScanner;
  57. import org.apache.tools.ant.Project;
  58. import org.apache.tools.ant.types.ZipFileSet;
  59. import org.apache.tools.ant.types.EnumeratedAttribute;
  60. import org.apache.tools.zip.ZipOutputStream;
  61. import java.io.IOException;
  62. import java.io.File;
  63. import java.io.InputStream;
  64. import java.io.Reader;
  65. import java.io.FileReader;
  66. import java.io.ByteArrayOutputStream;
  67. import java.io.PrintWriter;
  68. import java.io.ByteArrayInputStream;
  69. import java.io.OutputStreamWriter;
  70. import java.io.InputStreamReader;
  71. import java.util.Enumeration;
  72. /**
  73. * Creates a JAR archive.
  74. *
  75. * @author James Davidson <a href="mailto:duncan@x180.com">duncan@x180.com</a>
  76. * @author Brian Deitte <a href="mailto:bdeitte@macromedia.com">bdeitte@macromedia.com</a>
  77. *
  78. * @ant.task category="packaging"
  79. */
  80. public class Jar extends Zip {
  81. /** The index file name. */
  82. private final static String INDEX_NAME = "META-INF/INDEX.LIST";
  83. /** merged manifests added through addConfiguredManifest */
  84. private Manifest configuredManifest;
  85. /** merged manifests added through filesets */
  86. private Manifest filesetManifest;
  87. /**
  88. * whether to merge fileset manifests;
  89. * value is true if filesetmanifest is 'merge' or 'mergewithoutmain'
  90. */
  91. private boolean mergeManifests = false;
  92. /**
  93. * whether to merge the main section of fileset manifests;
  94. * value is true if filesetmanifest is 'merge'
  95. */
  96. private boolean mergeManifestsMain = false;
  97. /** the manifest specified by the 'manifest' attribute **/
  98. private Manifest manifest;
  99. /**
  100. * The file found from the 'manifest' attribute. This can be either the location of a manifest,
  101. * or the name of a jar added through a fileset. If its the name of an added jar, the manifest is
  102. * looked for in META-INF/MANIFEST.MF
  103. */
  104. private File manifestFile;
  105. /** jar index is JDK 1.3+ only */
  106. private boolean index = false;
  107. /** constructor */
  108. public Jar() {
  109. super();
  110. archiveType = "jar";
  111. emptyBehavior = "create";
  112. setEncoding("UTF8");
  113. }
  114. public void setWhenempty(WhenEmpty we) {
  115. log("JARs are never empty, they contain at least a manifest file",
  116. Project.MSG_WARN);
  117. }
  118. /**
  119. * @deprecated Use setDestFile(File) instead
  120. */
  121. public void setJarfile(File jarFile) {
  122. log("DEPRECATED - The jarfile attribute is deprecated. Use destfile attribute instead.");
  123. setDestFile(jarFile);
  124. }
  125. /**
  126. * Set whether or not to create an index list for classes
  127. * to speed up classloading.
  128. */
  129. public void setIndex(boolean flag){
  130. index = flag;
  131. }
  132. public void addConfiguredManifest(Manifest newManifest) throws ManifestException {
  133. if (configuredManifest == null) {
  134. configuredManifest = newManifest;
  135. }
  136. else {
  137. configuredManifest.merge(newManifest);
  138. }
  139. }
  140. public void setManifest(File manifestFile) {
  141. if (!manifestFile.exists()) {
  142. throw new BuildException("Manifest file: " + manifestFile + " does not exist.",
  143. getLocation());
  144. }
  145. this.manifestFile = manifestFile;
  146. }
  147. private Manifest getManifest(File manifestFile) {
  148. Manifest newManifest = null;
  149. Reader r = null;
  150. try {
  151. r = new FileReader(manifestFile);
  152. newManifest = getManifest(r);
  153. }
  154. catch (IOException e) {
  155. throw new BuildException("Unable to read manifest file: " + manifestFile, e);
  156. }
  157. finally {
  158. if (r != null) {
  159. try {
  160. r.close();
  161. }
  162. catch (IOException e) {
  163. // do nothing
  164. }
  165. }
  166. }
  167. return newManifest;
  168. }
  169. private Manifest getManifest(Reader r) {
  170. Manifest newManifest = null;
  171. try {
  172. newManifest = new Manifest(r);
  173. }
  174. catch (ManifestException e) {
  175. log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR);
  176. throw new BuildException("Invalid Manifest: " + manifestFile, e, getLocation());
  177. }
  178. catch (IOException e) {
  179. throw new BuildException("Unable to read manifest file", e);
  180. }
  181. return newManifest;
  182. }
  183. public void setFilesetmanifest(FilesetManifestConfig config) {
  184. String filesetManifestConfig = config.getValue();
  185. mergeManifests = ! "skip".equals(filesetManifestConfig);
  186. mergeManifestsMain = "merge".equals(filesetManifestConfig);
  187. }
  188. public void addMetainf(ZipFileSet fs) {
  189. // We just set the prefix for this fileset, and pass it up.
  190. fs.setPrefix("META-INF/");
  191. super.addFileset(fs);
  192. }
  193. protected void initZipOutputStream(ZipOutputStream zOut)
  194. throws IOException, BuildException
  195. {
  196. String ls = System.getProperty("line.separator");
  197. try {
  198. Manifest finalManifest = Manifest.getDefaultManifest();
  199. if (manifest == null) {
  200. if (manifestFile != null) {
  201. // if we haven't got the manifest yet, attempt to get it now and
  202. // have manifest be the final merge
  203. manifest = getManifest(manifestFile);
  204. finalManifest.merge(filesetManifest);
  205. finalManifest.merge(configuredManifest);
  206. finalManifest.merge(manifest, ! mergeManifestsMain);
  207. }
  208. else if (configuredManifest != null) {
  209. // configuredManifest is the final merge
  210. finalManifest.merge(filesetManifest);
  211. finalManifest.merge(configuredManifest, ! mergeManifestsMain);
  212. }
  213. else if (filesetManifest != null) {
  214. // filesetManifest is the final (and only) merge
  215. finalManifest.merge(filesetManifest, ! mergeManifestsMain);
  216. }
  217. } else {
  218. // manifest is the final merge
  219. finalManifest.merge(filesetManifest);
  220. finalManifest.merge(configuredManifest);
  221. finalManifest.merge(manifest, ! mergeManifestsMain);
  222. }
  223. for (Enumeration e = finalManifest.getWarnings(); e.hasMoreElements(); ) {
  224. log("Manifest warning: " + (String)e.nextElement(), Project.MSG_WARN);
  225. }
  226. // need to set the line.separator as \r\n due to a bug with the jar verifier
  227. System.setProperty("line.separator", "\r\n");
  228. zipDir(null, zOut, "META-INF/");
  229. // time to write the manifest
  230. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  231. PrintWriter writer = new PrintWriter(baos);
  232. finalManifest.write(writer);
  233. writer.flush();
  234. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  235. super.zipFile(bais, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis(), null);
  236. super.initZipOutputStream(zOut);
  237. }
  238. catch (ManifestException e) {
  239. log("Manifest is invalid: " + e.getMessage(), Project.MSG_ERR);
  240. throw new BuildException("Invalid Manifest", e, getLocation());
  241. }
  242. finally {
  243. System.setProperty("line.separator", ls);
  244. }
  245. }
  246. protected void finalizeZipOutputStream(ZipOutputStream zOut)
  247. throws IOException, BuildException {
  248. if (index) {
  249. createIndexList(zOut);
  250. }
  251. }
  252. /**
  253. * Create the index list to speed up classloading.
  254. * This is a JDK 1.3+ specific feature and is disabled by default.
  255. * {@link http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index}
  256. * @param zOut the zip stream representing the jar being built.
  257. * @throws IOException thrown if there is an error while creating the
  258. * index and adding it to the zip stream.
  259. */
  260. private void createIndexList(ZipOutputStream zOut) throws IOException {
  261. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  262. // encoding must be UTF8 as specified in the specs.
  263. PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, "UTF8"));
  264. // version-info blankline
  265. writer.println("JarIndex-Version: 1.0");
  266. writer.println();
  267. // header newline
  268. writer.println(zipFile.getName());
  269. // JarIndex is sorting the directories by ascending order.
  270. // it's painful to do in JDK 1.1 and it has no value but cosmetic
  271. // since it will be read into a hashtable by the classloader.
  272. Enumeration enum = addedDirs.keys();
  273. while (enum.hasMoreElements()) {
  274. String dir = (String)enum.nextElement();
  275. // try to be smart, not to be fooled by a weird directory name
  276. // @fixme do we need to check for directories starting by ./ ?
  277. dir = dir.replace('\\', '/');
  278. int pos = dir.lastIndexOf('/');
  279. if (pos != -1){
  280. dir = dir.substring(0, pos);
  281. }
  282. // looks like nothing from META-INF should be added
  283. // and the check is not case insensitive.
  284. // see sun.misc.JarIndex
  285. if ( dir.startsWith("META-INF") ){
  286. continue;
  287. }
  288. // name newline
  289. writer.println(dir);
  290. }
  291. writer.flush();
  292. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  293. super.zipFile(bais, zOut, INDEX_NAME, System.currentTimeMillis(), null);
  294. }
  295. /**
  296. * Overriden from Zip class to deal with manifests
  297. */
  298. protected void zipFile(File file, ZipOutputStream zOut, String vPath)
  299. throws IOException
  300. {
  301. if (vPath.equalsIgnoreCase("META-INF/MANIFEST.MF")) {
  302. filesetManifest(file, null);
  303. } else {
  304. super.zipFile(file, zOut, vPath);
  305. }
  306. }
  307. /**
  308. * Overriden from Zip class to deal with manifests
  309. */
  310. protected void zipFile(InputStream is, ZipOutputStream zOut, String vPath, long lastModified, File file)
  311. throws IOException
  312. {
  313. if (vPath.equalsIgnoreCase("META-INF/MANIFEST.MF")) {
  314. filesetManifest(file, is);
  315. } else {
  316. super.zipFile(is, zOut, vPath, lastModified, null);
  317. }
  318. }
  319. private void filesetManifest(File file, InputStream is) {
  320. if (manifestFile.equals(file)) {
  321. // If this is the same name specified in 'manifest', this is the manifest to use
  322. log("Found manifest " + file, Project.MSG_VERBOSE);
  323. if (is != null) {
  324. manifest = getManifest(new InputStreamReader(is));
  325. }
  326. else {
  327. manifest = getManifest(file);
  328. }
  329. }
  330. else if (mergeManifests) {
  331. // we add this to our group of fileset manifests
  332. log("Found manifest to merge in file " + file, Project.MSG_VERBOSE);
  333. try
  334. {
  335. Manifest newManifest = getManifest(new InputStreamReader(is));
  336. if (filesetManifest == null) {
  337. filesetManifest = newManifest;
  338. } else {
  339. filesetManifest.merge(newManifest);
  340. }
  341. }
  342. catch (ManifestException e) {
  343. log("Manifest in file " + file + " is invalid: " + e.getMessage(), Project.MSG_ERR);
  344. throw new BuildException("Invalid Manifest", e, getLocation());
  345. }
  346. }
  347. else {
  348. // assuming 'skip' otherwise
  349. log("File " + file + " includes a META-INF/MANIFEST.MF which will be ignored. " +
  350. "To include this file, set filesetManifest to a value other than 'skip'.", Project.MSG_WARN);
  351. }
  352. }
  353. /**
  354. * Check whether the archive is up-to-date;
  355. * @param scanners list of prepared scanners containing files to archive
  356. * @param zipFile intended archive file (may or may not exist)
  357. * @return true if nothing need be done (may have done something already); false if
  358. * archive creation should proceed
  359. * @exception BuildException if it likes
  360. */
  361. protected boolean isUpToDate(FileScanner[] scanners, File zipFile) throws BuildException {
  362. // need to handle manifest as a special check
  363. if (configuredManifest != null || manifestFile == null) {
  364. java.util.zip.ZipFile theZipFile = null;
  365. try {
  366. theZipFile = new java.util.zip.ZipFile(zipFile);
  367. java.util.zip.ZipEntry entry = theZipFile.getEntry("META-INF/MANIFEST.MF");
  368. if (entry == null) {
  369. log("Updating jar since the current jar has no manifest", Project.MSG_VERBOSE);
  370. return false;
  371. }
  372. Manifest currentManifest = new Manifest(new InputStreamReader(theZipFile.getInputStream(entry)));
  373. if (configuredManifest == null) {
  374. configuredManifest = Manifest.getDefaultManifest();
  375. }
  376. if (!currentManifest.equals(configuredManifest)) {
  377. log("Updating jar since jar manifest has changed", Project.MSG_VERBOSE);
  378. return false;
  379. }
  380. }
  381. catch (Exception e) {
  382. // any problems and we will rebuild
  383. log("Updating jar since cannot read current jar manifest: " + e.getClass().getName() + e.getMessage(),
  384. Project.MSG_VERBOSE);
  385. return false;
  386. }
  387. finally {
  388. if (theZipFile != null) {
  389. try {
  390. theZipFile.close();
  391. }
  392. catch (IOException e) {
  393. //ignore
  394. }
  395. }
  396. }
  397. }
  398. else if (manifestFile.lastModified() > zipFile.lastModified()) {
  399. return false;
  400. }
  401. return super.isUpToDate(scanners, zipFile);
  402. }
  403. protected boolean createEmptyZip(File zipFile) {
  404. // Jar files always contain a manifest and can never be empty
  405. return true;
  406. }
  407. /**
  408. * Make sure we don't think we already have a MANIFEST next time this task
  409. * gets executed.
  410. */
  411. protected void cleanUp() {
  412. super.cleanUp();
  413. configuredManifest = null;
  414. filesetManifest = null;
  415. mergeManifests = false;
  416. mergeManifestsMain = false;
  417. manifest = null;
  418. manifestFile = null;
  419. index = false;
  420. }
  421. public static class FilesetManifestConfig extends EnumeratedAttribute {
  422. public String[] getValues() {
  423. return new String[] {"skip", "merge", "mergewithoutmain"};
  424. }
  425. }
  426. }