diff --git a/build.xml b/build.xml
index b042d1e08..9950d5b6e 100644
--- a/build.xml
+++ b/build.xml
@@ -90,7 +90,6 @@
<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.
basedir | the directory from which to jar the files. | -Yes | +No |
compress | @@ -2079,6 +2088,11 @@ elements.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 @@ 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).
<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.
basedir | the directory from which to zip the files. | -Yes | +No |
compress | @@ -3707,6 +3749,11 @@ elements. ("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 @@ or files with the nametodo.html
are excluded.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
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; 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); } } } 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 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; + } - 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,