diff --git a/build.xml b/build.xml index b042d1e08..9950d5b6e 100644 --- a/build.xml +++ b/build.xml @@ -90,7 +90,6 @@ - @@ -102,6 +101,7 @@ forceoverwrite="true" filtering="on"> + diff --git a/docs/index.html b/docs/index.html index 10d5b433f..658aa2f3f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2022,6 +2022,15 @@ supports all attributes of <fileset> <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.

Parameters

@@ -2037,7 +2046,7 @@ elements.

- + @@ -2079,6 +2088,11 @@ elements.

+ + + + +
basedir the directory from which to jar the files.YesNo
compress the manifest file to use. No
whenemptyBehavior to use if no files match.No

Examples

  <jar jarfile="${dist}/lib/app.jar" basedir="${build}/classes" />
@@ -2100,6 +2114,21 @@ with the name Test.class are excluded.

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

+

Java

Description

@@ -3655,6 +3684,19 @@ supports all attributes of <fileset> <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.

Parameters

@@ -3670,7 +3712,7 @@ elements.

- + @@ -3707,6 +3749,11 @@ elements.

("yes"/"no"). Default excludes are used when omitted. + + + + +
basedir the directory from which to zip the files.YesNo
compress No
whenemptyBehavior when no files match.No

Examples

  <zip zipfile="${dist}/manual.zip"
@@ -3729,6 +3776,14 @@ or files with the name todo.html are excluded.

zips all files in the htdocs/manual directory in a file called manual.zip in the ${dist} directory. Only html files under the directory api are zipped, and files with the name todo.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 called manual.zip +in the ${dist} directory, and also adds the file ChangeLog.txt in the +current directory. ChangeLog.txt will be added to the top of the ZIP file, just as if +it had been located at htdocs/manual/ChangeLog.txt.


Optional tasks

diff --git a/src/main/org/apache/tools/ant/defaultManifest.mf b/src/main/org/apache/tools/ant/defaultManifest.mf index 9d885be53..ce5678fb9 100644 --- a/src/main/org/apache/tools/ant/defaultManifest.mf +++ b/src/main/org/apache/tools/ant/defaultManifest.mf @@ -1 +1,3 @@ Manifest-Version: 1.0 +Created-By: Ant @VERSION@ + diff --git a/src/main/org/apache/tools/ant/taskdefs/Jar.java b/src/main/org/apache/tools/ant/taskdefs/Jar.java index 37a30402b..7351b2ec3 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Jar.java +++ b/src/main/org/apache/tools/ant/taskdefs/Jar.java @@ -86,38 +86,67 @@ public class Jar extends Zip { 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; i zipFile.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); } } } diff --git a/src/main/org/apache/tools/ant/taskdefs/Zip.java b/src/main/org/apache/tools/ant/taskdefs/Zip.java index 0979341d2..a961b4e7f 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Zip.java +++ b/src/main/org/apache/tools/ant/taskdefs/Zip.java @@ -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 @@ public class Zip extends MatchingTask { 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 @@ public class Zip extends MatchingTask { 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); + } + + /** + * Adds a reference to a set of files (nested filesetref element). + */ + public void addFilesetref(Reference ref) { + filesets.addElement(ref); + } + + /** + * 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 is skip; + * 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; + } - DirectoryScanner ds = super.getDirectoryScanner(baseDir); + 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!"); - String[] files = ds.getIncludedFiles(); - String[] dirs = ds.getIncludedDirectories(); + 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 @@ public class Zip extends MatchingTask { { } + /** + * 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.addElement(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,