Return-Path: <include>
, <exclude>
,
<patternset>
and <patternsetref>
elements.
You can also use nested file sets for more flexibility, and specify +multiple ones to merge together different trees of files into one JAR. +See the Zip task for more details and examples.
+If the manifest is omitted, a simple one will be supplied by Ant. +You should not include META-INF/MANIFEST.MF in your set of files. +
The whenempty
parameter controls what happens when no files match.
+If create
(the default), the JAR is created anyway with only a manifest.
+If skip
, the JAR is not created and a warning is issued.
+If fail
, the JAR is not created and the build is halted with an error.
basedir | the directory from which to jar the files. | -Yes | +No |
compress | @@ -2079,6 +2088,11 @@the manifest file to use. | No | |
whenempty | +Behavior to use if no files match. | +No | +
<jar jarfile="${dist}/lib/app.jar" basedir="${build}/classes" />@@ -2100,6 +2114,21 @@ called
app.jar
in the ${dist}/lib
directory. Only
files under the directory mypackage/test
are used, and files with
the name Test.class
are excluded.
+<jar jarfile="${dist}/lib/app.jar"> + <fileset dir="${build}/classes" + excludes="**/Test.class" + /> + <fileset dir="${src}/resources"/> + </jar>+
jars all files in the ${build}/classes
directory and also
+in the ${src}/resources
directory together in a file
+called app.jar
in the ${dist}/lib
directory.
+Files with the name Test.class
are excluded.
+If there are files such as ${build}/classes/mypackage/MyClass.class
+and ${src}/resources/mypackage/image.gif
, they will appear
+in the same directory in the JAR (and thus be considered in the same package
+by Java).
<include>
, <exclude>
,
<patternset>
and <patternsetref>
elements.
+Or, you may place within it nested file sets, or references to file sets.
+In this case basedir
is optional; the implicit file set is only used
+if basedir
is set. You may use any mixture of the implicit file set
+(with basedir
set, and optional attributes like includes
+and optional subelements like <include>
); explicit nested
+<fileset>
elements; and nested <filesetref>
+elements; so long as at least one fileset total is specified. The ZIP file will
+only reflect the relative paths of files within each fileset.
The whenempty
parameter controls what happens when no files match.
+If skip
(the default), the ZIP is not created and a warning is issued.
+If fail
, the ZIP is not created and the build is halted with an error.
+If create
, an empty ZIP file (explicitly zero entries) is created,
+which should be recognized as such by compliant ZIP manipulation tools.
basedir | the directory from which to zip the files. | -Yes | +No |
compress | @@ -3707,6 +3749,11 @@ ("yes"/"no"). Default excludes are used when omitted.No | ||
whenempty | +Behavior when no files match. | +No | +
<zip zipfile="${dist}/manual.zip" @@ -3729,6 +3776,14 @@zips all files in the
+htdocs/manual
directory in a file calledmanual.zip
in the${dist}
directory. Only html files under the directoryapi
are zipped, and files with the nametodo.html
are excluded.<zip zipfile="${dist}/manual.zip"> + <fileset dir="htdocs/manual"/> + <fileset dir="." includes="ChangeLog.txt"/> + </zip>+zips all files in the
htdocs/manual
directory in a file calledmanual.zip
+in the${dist}
directory, and also adds the fileChangeLog.txt
in the +current directory.ChangeLog.txt
will be added to the top of the ZIP file, just as if +it had been located athtdocs/manual/ChangeLog.txt
.
Optional tasks
Index: src/main/org/apache/tools/ant/defaultManifest.mf =================================================================== RCS file: /home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/defaultManifest.mf,v retrieving revision 1.1 diff -d -u -r1.1 defaultManifest.mf --- src/main/org/apache/tools/ant/defaultManifest.mf 2000/01/13 10:41:40 1.1 +++ src/main/org/apache/tools/ant/defaultManifest.mf 2000/08/30 13:41:32 @@ -1 +1,3 @@ Manifest-Version: 1.0 +Created-By: Ant @VERSION@ + Index: src/main/org/apache/tools/ant/taskdefs/Jar.java =================================================================== RCS file: /home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Jar.java,v retrieving revision 1.5 diff -d -u -r1.5 Jar.java --- src/main/org/apache/tools/ant/taskdefs/Jar.java 2000/06/27 11:12:11 1.5 +++ src/main/org/apache/tools/ant/taskdefs/Jar.java 2000/08/30 13:41:33 @@ -86,38 +86,67 @@ super.zipDir(new File(manifest.getParent()), zOut, "META-INF/"); super.zipFile(manifest, zOut, "META-INF/MANIFEST.MF"); } else { - /* - * We don't store directories at all and this one will cause a lot - * of problems with STORED Zip-Mode. - * - * That's why i've removed it -- Stefan Bodewig - */ - // ZipEntry ze = new ZipEntry("META-INF/"); - // zOut.putNextEntry(ze); String s = "/org/apache/tools/ant/defaultManifest.mf"; InputStream in = this.getClass().getResourceAsStream(s); if ( in == null ) throw new BuildException ( "Could not find: " + s ); + super.zipDir(null, zOut, "META-INF/"); zipFile(in, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis()); } } + protected boolean isUpToDate(FileScanner[] scanners, File zipFile) + { + File[] files = grabFiles(scanners); + if (emptyBehavior == null) emptyBehavior = "create"; + if (files.length == 0) { + if (emptyBehavior.equals("skip")) { + log("Warning: skipping JAR archive " + zipFile + + " because no files were included.", Project.MSG_WARN); + return true; + } else if (emptyBehavior.equals("fail")) { + throw new BuildException("Cannot create JAR archive " + zipFile + + ": no files were included.", location); + } else { + // create + if (!zipFile.exists() || + (manifest != null && + manifest.lastModified() > zipFile.lastModified())) + log("Note: creating empty JAR archive " + zipFile, Project.MSG_INFO); + // and continue below... + } + } + if (!zipFile.exists()) return false; + if (manifest != null && manifest.lastModified() > zipFile.lastModified()) + return false; + for (int i=0; izipFile.lastModified()) { + return false; + } + } + return true; + } + protected void zipDir(File dir, ZipOutputStream zOut, String vPath) throws IOException { // First add directory to zip entry - if(!vPath.equals("META-INF/")) { + if(!vPath.equalsIgnoreCase("META-INF/")) { // we already added a META-INF super.zipDir(dir, zOut, vPath); } + // no warning if not, it is harmless in and of itself } protected void zipFile(File file, ZipOutputStream zOut, String vPath) throws IOException { // We already added a META-INF/MANIFEST.MF - if (!vPath.equals("META-INF/MANIFEST.MF")) { + if (!vPath.equalsIgnoreCase("META-INF/MANIFEST.MF")) { super.zipFile(file, zOut, vPath); + } else { + log("Warning: selected JAR files include a META-INF/MANIFEST.MF which will be ignored " + + "(please use manifest attribute to jar task)", Project.MSG_WARN); } } } Index: src/main/org/apache/tools/ant/taskdefs/Zip.java =================================================================== RCS file: /home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Zip.java,v retrieving revision 1.10 diff -d -u -r1.10 Zip.java --- src/main/org/apache/tools/ant/taskdefs/Zip.java 2000/08/03 09:13:18 1.10 +++ src/main/org/apache/tools/ant/taskdefs/Zip.java 2000/08/30 13:41:33 @@ -55,9 +55,11 @@ package org.apache.tools.ant.taskdefs; import org.apache.tools.ant.*; +import org.apache.tools.ant.types.*; import java.io.*; import java.util.Enumeration; +import java.util.Hashtable; import java.util.StringTokenizer; import java.util.Vector; import java.util.zip.*; @@ -75,6 +77,10 @@ private File baseDir; private boolean doCompress = true; protected String archiveType = "zip"; + // For directories: + private static long emptyCrc = new CRC32 ().getValue (); + protected String emptyBehavior = null; + private Vector filesets = new Vector (); /** * This is the name/location of where to @@ -99,73 +105,113 @@ doCompress = Project.toBoolean(compress); } - public void execute() throws BuildException { - if (baseDir == null) { - throw new BuildException("basedir attribute must be set!"); - } - if (!baseDir.exists()) { - throw new BuildException("basedir does not exist!"); - } + /** + * Adds a set of files (nested fileset attribute). + */ + public void addFileset(FileSet set) { + filesets.addElement(set); + } - DirectoryScanner ds = super.getDirectoryScanner(baseDir); + /** + * Adds a reference to a set of files (nested filesetref element). + */ + public void addFilesetref(Reference ref) { + filesets.addElement(ref); + } - String[] files = ds.getIncludedFiles(); - String[] dirs = ds.getIncludedDirectories(); + /** + * Sets behavior of the task when no files match. + * Possible values are: fail
(throw an exception + * and halt the build);skip
(do not create + * any archive, but issue a warning);create
+ * (make an archive with no entries). + * Default for zip tasks isskip
; + * for jar tasks,create
. + */ + public void setWhenempty(String we) throws BuildException { + we = we.toLowerCase(); + // XXX could instead be using EnumeratedAttribute, but this works + if (!"fail".equals(we) && !"skip".equals(we) && !"create".equals(we)) + throw new BuildException("Unrecognized whenempty attribute: " + we); + emptyBehavior = we; + } + + public void execute() throws BuildException { + if (baseDir == null && filesets.size() == 0) + throw new BuildException("basedir attribute must be set, or at least one fileset must be given!"); + + Vector dss = new Vector (); + if (baseDir != null) + dss.addElement(getDirectoryScanner(baseDir)); + for (int i=0; i- zipFile.lastModified()) - upToDate = false; - if (upToDate) return; + // can also handle empty archives + if (isUpToDate(scanners, zipFile)) return; log("Building "+ archiveType +": "+ zipFile.getAbsolutePath()); - ZipOutputStream zOut = null; - try { - zOut = new ZipOutputStream(new FileOutputStream(zipFile)); - if (doCompress) { - zOut.setMethod(ZipOutputStream.DEFLATED); - } else { - zOut.setMethod(ZipOutputStream.STORED); - } - initZipOutputStream(zOut); + try { + ZipOutputStream zOut = new ZipOutputStream(new FileOutputStream(zipFile)); + try { + if (doCompress) { + zOut.setMethod(ZipOutputStream.DEFLATED); + } else { + zOut.setMethod(ZipOutputStream.STORED); + } + initZipOutputStream(zOut); - for (int i = 0; i < dirs.length; i++) { - File f = new File(baseDir,dirs[i]); - String name = dirs[i].replace(File.separatorChar,'/')+"/"; - zipDir(f, zOut, name); - } + // XXX ideally would also enter includedDirectories to the archive + Hashtable parentDirs = new Hashtable(); - for (int i = 0; i < files.length; i++) { - File f = new File(baseDir,files[i]); - String name = files[i].replace(File.separatorChar,'/'); - zipFile(f, zOut, name); - } - } catch (IOException ioe) { - String msg = "Problem creating " + archiveType + " " + ioe.getMessage(); + for (int j = 0; j < scanners.length; j++) { + String[] files = scanners[j].getIncludedFiles(); + File thisBaseDir = scanners[j].getBasedir(); + for (int i = 0; i < files.length; i++) { + File f = new File(thisBaseDir,files[i]); + String name = files[i].replace(File.separatorChar,'/'); + // Look for & create parent dirs as needed. + int slashPos = -1; + while ((slashPos = name.indexOf((int)'/', slashPos + 1)) != -1) { + String dir = name.substring(0, slashPos); + if (!parentDirs.contains(dir)) { + parentDirs.put(dir, dir); + zipDir(new File(thisBaseDir, dir.replace('/', File.separatorChar)), + zOut, dir + '/'); + } + } + zipFile(f, zOut, name); + } + } + } finally { + zOut.close (); + } + } catch (IOException ioe) { + String msg = "Problem creating " + archiveType + ": " + ioe.getMessage(); // delete a bogus ZIP file - if (zOut != null) { - try { - zOut.close(); - zOut = null; - } catch (IOException e) {} - if (!zipFile.delete()) { - msg = zipFile + " is probably corrupt but I could not delete it"; - } - } + if (!zipFile.delete()) { + msg += " (and the archive is probably corrupt but I could not delete it)"; + } throw new BuildException(msg, ioe, location); - } finally { - if (zOut != null) { - try { - // close up - zOut.close(); - } - catch (IOException e) {} - } } } @@ -174,9 +220,87 @@ { } + /** + * Check whether the archive is up-to-date; and handle behavior for empty archives. + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); false if + * archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate(FileScanner[] scanners, File zipFile) throws BuildException + { + if (emptyBehavior == null) emptyBehavior = "skip"; + File[] files = grabFiles(scanners); + if (files.length == 0) { + if (emptyBehavior.equals("skip")) { + log("Warning: skipping ZIP archive " + zipFile + + " because no files were included.", Project.MSG_WARN); + return true; + } else if (emptyBehavior.equals("fail")) { + throw new BuildException("Cannot create ZIP archive " + zipFile + + ": no files were included.", location); + } else { + // Create. + if (zipFile.exists()) return true; + // In this case using java.util.zip will not work + // because it does not permit a zero-entry archive. + // Must create it manually. + log("Note: creating empty ZIP archive " + zipFile, Project.MSG_INFO); + try { + OutputStream os = new FileOutputStream(zipFile); + try { + // Cf. PKZIP specification. + byte[] empty = new byte[22]; + empty[0] = 80; // P + empty[1] = 75; // K + empty[2] = 5; + empty[3] = 6; + // remainder zeros + os.write(empty); + } finally { + os.close(); + } + } catch (IOException ioe) { + throw new BuildException("Could not create empty ZIP archive", ioe, location); + } + return true; + } + } else { + // Probably unnecessary but just for clarity: + if (!zipFile.exists()) return false; + for (int i=0; i zipFile.lastModified()) { + return false; + } + } + return true; + } + } + + protected static File[] grabFiles(FileScanner[] scanners) { + Vector files = new Vector (); + for (int i = 0; i < scanners.length; i++) { + File thisBaseDir = scanners[i].getBasedir(); + String[] ifiles = scanners[i].getIncludedFiles(); + for (int j = 0; j < ifiles.length; j++) + files.add(new File(thisBaseDir, ifiles[j])); + } + File[] toret = new File[files.size()]; + files.copyInto(toret); + return toret; + } + protected void zipDir(File dir, ZipOutputStream zOut, String vPath) throws IOException { + ZipEntry ze = new ZipEntry (vPath); + if (dir != null) ze.setTime (dir.lastModified ()); + ze.setSize (0); + ze.setMethod (ZipEntry.STORED); + // This is faintly ridiculous: + ze.setCrc (emptyCrc); + zOut.putNextEntry (ze); } protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath, --------------4F1056F97045F806F783315F Content-Type: application/x-zip-compressed; name="zip-jar-test.zip" Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="zip-jar-test.zip" UEsDBAoAAAAAAIRMHikAAAAAAAAAAAAAAAAUABUAemlwamFyL2VtcHR5Ly5leGlzdHNVVAkA A2e5rDlnuaw5VXgEAPQB9AFQSwMECgAAAAAAvGIeKQAAAAAAAAAAAAAAABUAFQB6aXBqYXIv c29tZXRoaW5nL3JlYWxVVAkAA0PgrDlS4Kw5VXgEAPQB9AFQSwMECgAAAAAAXU4eKQAAAAAA AAAAAAAAACoAFQB6aXBqYXIvc29tZXRoaW5nL3N1YmRpcjEvc3ViZGlyMi9zdWJkaXIzL3lV VAkAA+G8rDlS4Kw5VXgEAPQB9AFQSwMECgAAAAAAWU4eKQAAAAAAAAAAAAAAABoAFQB6aXBq YXIvc29tZXRoaW5nL3N1YmRpcjEveFVUCQAD2bysOVLgrDlVeAQA9AH0AVBLAwQUAAAACABp TR4pSSQE+D4AAACHAAAAFwAVAHppcGphci9zb21ldGhpbmcvYmlnZ2VyVVQJAAMWu6w5bAyt OVV4BAD0AfQBC87PTVUoSa0oUchILUrV43LMS1EoBonl5hdhlwCLleQrJOfnFhSlFhcjyYP1 FKWmlOalJOaV4FOJsAIiBgBQSwMEFAAAAAgAo2IeKWiYdZlRAgAAlA4AABAAFQB6aXBqYXIv YnVpbGQueG1sVVQJAAMR4Kw5bAytOVV4BAD0AfQBtVfBkqMgEL3PV7Aep2KM5LYV518YbBJn 0aSE1Dr79QutTKVMGmKVOQwqr+n3Gh8d53Dpz18gLetEC1VmwdiM1aDEVdsqE1pn7FMYqJu+ yrbZxxtjByv6I4QFGFHDBbraVNm/5rLb+MHgKP1Ybr5Ev8PB4Cj9iLMcB5zlOLv3w+BX5cpM l7xtBqjxoQcVJv2ti/XXrEBZv/KcmdP5qmumRKN/M69AbZinHC9csTy/L8HHYWUOcPd+nWr0 tBm7rXu82QNoL/Y7YzBIfa3Blfz+XmxhaIw1k45iTP6Yx9BEZmT6e4IOSarM/FmTWtHU6o7a 7+B61JKmlnfUsgdhYTXykuQuZ6/WnFuwp6Y7prJ6R4Ws7t5b69Yw7nEd9XhkaCIzMr3EMHho aGp1R72eYbBF0NTyjnpNw/jORHKXs1e7xDCczMrHrK3oGuUeq8zfbVu1WkGc9hCnPPRCNbSt OGWrF6qhncZpp71Oz56Us0/bZIkfB5JomDewwfbCkz3RbN0vM9luHYYNd8RdhIeMW/1Iu+dx wc8Qjt8HMdox4ily1tRuZ78R/Ql24dPrnIg/m+MR+qDUS5zSJdKDNrCwPv99Q5bmwIdVuXnm /m5rWbyrUeIRX8i91raEzz7KwCiNOCxLdsqliAux56s8uZaggxScYKOSn+oKjEicHVyJh+xB qiA+kUNqEF1YX4MGC2z+IVuQsEngKoHLOF4mluMLi6mL4yqByzhexmGegBPqeEIdT6jbx+Eh Doe+Gwu46ZB0WDjx0UTpoHA6Z24+FNN/oh9v/wFQSwMECgAAAAAAxWIeKZEvFH0xAAAAMQAA AA4AFQB6aXBqYXIvbWFuaS5tZlVUCQADUeCsOWwMrTlVeAQA9AH0AU1hbmlmZXN0LVZlcnNp b246IDEuMApYLUNvbW1lbnQ6IFNvbWV0aGluZyBoZXJlCgpQSwMECgAAAAAAEVkeKQAAAAAA AAAAAAAAABQAFQB6aXBqYXIvZXh0cmFtYW5pL2Zvb1VUCQADEc+sOZHcrDlVeAQA9AH0AVBL AwQKAAAAAAAhWR4pNGNaejUAAAA1AAAAJQAVAHppcGphci9leHRyYW1hbmkvTUVUQS1JTkYv TUFOSUZFU1QuTUZVVAkAAy3PrDlsDK05VXgEAPQB9AFNYW5pZmVzdC1WZXJzaW9uOiAxLjAK WC1Db21tZW50OiBzaG91bGQgbm90IGJlIHVzZWQKClBLAwQKAAAAAAAHYR4pAAAAAAAAAAAA AAAAGAAVAHppcGphci9zb21ldGhpbmdlbHNlL3doeVVUCQADDd2sOUXgrDlVeAQA9AH0AVBL AwQKAAAAAAAaYR4pAAAAAAAAAAAAAAAAKwAVAHppcGphci9zb21ldGhpbmdlbHNlL3N1YmRp cjEvc3ViZGlyMmEvd2hlcmVVVAkAAzTdrDlF4Kw5VXgEAPQB9AFQSwMECgAAAAAAF2EeKQAA AAAAAAAAAAAAACAAFQB6aXBqYXIvc29tZXRoaW5nZWxzZS9zdWJkaXIxL2hvd1VUCQADLd2s OUXgrDlVeAQA9AH0AVBLAQIWAwoAAAAAAIRMHikAAAAAAAAAAAAAAAAUAA0AAAAAAAAAAAC0 gQAAAAB6aXBqYXIvZW1wdHkvLmV4aXN0c1VUBQADZ7msOVV4AABQSwECFgMKAAAAAAC8Yh4p AAAAAAAAAAAAAAAAFQANAAAAAAAAAAAAtIFHAAAAemlwamFyL3NvbWV0aGluZy9yZWFsVVQF AAND4Kw5VXgAAFBLAQIWAwoAAAAAAF1OHikAAAAAAAAAAAAAAAAqAA0AAAAAAAAAAAC0gY8A AAB6aXBqYXIvc29tZXRoaW5nL3N1YmRpcjEvc3ViZGlyMi9zdWJkaXIzL3lVVAUAA+G8rDlV eAAAUEsBAhYDCgAAAAAAWU4eKQAAAAAAAAAAAAAAABoADQAAAAAAAAAAALSB7AAAAHppcGph ci9zb21ldGhpbmcvc3ViZGlyMS94VVQFAAPZvKw5VXgAAFBLAQIWAxQAAAAIAGlNHilJJAT4 PgAAAIcAAAAXAA0AAAAAAAEAAAC0gTkBAAB6aXBqYXIvc29tZXRoaW5nL2JpZ2dlclVUBQAD FrusOVV4AABQSwECFgMUAAAACACjYh4paJh1mVECAACUDgAAEAANAAAAAAABAAAAtIHBAQAA emlwamFyL2J1aWxkLnhtbFVUBQADEeCsOVV4AABQSwECFgMKAAAAAADFYh4pkS8UfTEAAAAx AAAADgANAAAAAAABAAAAtIFVBAAAemlwamFyL21hbmkubWZVVAUAA1HgrDlVeAAAUEsBAhYD CgAAAAAAEVkeKQAAAAAAAAAAAAAAABQADQAAAAAAAAAAALSBxwQAAHppcGphci9leHRyYW1h bmkvZm9vVVQFAAMRz6w5VXgAAFBLAQIWAwoAAAAAACFZHik0Y1p6NQAAADUAAAAlAA0AAAAA AAEAAAC0gQ4FAAB6aXBqYXIvZXh0cmFtYW5pL01FVEEtSU5GL01BTklGRVNULk1GVVQFAAMt z6w5VXgAAFBLAQIWAwoAAAAAAAdhHikAAAAAAAAAAAAAAAAYAA0AAAAAAAAAAAC0gZsFAAB6 aXBqYXIvc29tZXRoaW5nZWxzZS93aHlVVAUAAw3drDlVeAAAUEsBAhYDCgAAAAAAGmEeKQAA AAAAAAAAAAAAACsADQAAAAAAAAAAALSB5gUAAHppcGphci9zb21ldGhpbmdlbHNlL3N1YmRp cjEvc3ViZGlyMmEvd2hlcmVVVAUAAzTdrDlVeAAAUEsBAhYDCgAAAAAAF2EeKQAAAAAAAAAA AAAAACAADQAAAAAAAAAAALSBRAYAAHppcGphci9zb21ldGhpbmdlbHNlL3N1YmRpcjEvaG93 VVQFAAMt3aw5VXgAAFBLBQYAAAAADAAMAAIEAACXBgAAAAA= --------------4F1056F97045F806F783315F--