You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by co...@locus.apache.org on 2000/08/30 16:57:51 UTC
cvs commit: jakarta-ant/src/main/org/apache/tools/ant/taskdefs Jar.java Zip.java
conor 00/08/30 07:57:50
Modified: . build.xml
docs index.html
src/main/org/apache/tools/ant defaultManifest.mf
src/main/org/apache/tools/ant/taskdefs Jar.java Zip.java
Log:
Improvements to Zip and Jar tasks
This patch improves the robustness and error reporting of these tasks
especially when no files are to be included in the archives.
Submitted by: Jesse Glick <Je...@netbeans.com>
Revision Changes Path
1.63 +1 -1 jakarta-ant/build.xml
Index: build.xml
===================================================================
RCS file: /home/cvs/jakarta-ant/build.xml,v
retrieving revision 1.62
retrieving revision 1.63
diff -u -r1.62 -r1.63
--- build.xml 2000/08/21 15:05:52 1.62
+++ build.xml 2000/08/30 14:57:43 1.63
@@ -90,7 +90,6 @@
</javac>
<copydir src="${src.dir}" dest="${build.classes}">
- <include name="**/defaultManifest.mf" />
<include name="**/*.properties" />
</copydir>
@@ -102,6 +101,7 @@
forceoverwrite="true"
filtering="on">
<include name="**/version.txt" />
+ <include name="**/defaultManifest.mf" />
</copydir>
</target>
1.84 +57 -2 jakarta-ant/docs/index.html
Index: index.html
===================================================================
RCS file: /home/cvs/jakarta-ant/docs/index.html,v
retrieving revision 1.83
retrieving revision 1.84
diff -u -r1.83 -r1.84
--- index.html 2000/08/30 14:00:17 1.83
+++ index.html 2000/08/30 14:57:45 1.84
@@ -2022,6 +2022,15 @@
<code><include></code>, <code><exclude></code>,
<code><patternset></code> and <code><patternsetref></code>
elements.</p>
+<p>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 <a href="#zip">Zip</a> task for more details and examples.</p>
+<p>If the manifest is omitted, a simple one will be supplied by Ant.
+You should not include <samp>META-INF/MANIFEST.MF</samp> in your set of files.
+<p>The <code>whenempty</code> parameter controls what happens when no files match.
+If <code>create</code> (the default), the JAR is created anyway with only a manifest.
+If <code>skip</code>, the JAR is not created and a warning is issued.
+If <code>fail</code>, the JAR is not created and the build is halted with an error.
<h3>Parameters</h3>
<table border="1" cellpadding="2" cellspacing="0">
<tr>
@@ -2037,7 +2046,7 @@
<tr>
<td valign="top">basedir</td>
<td valign="top">the directory from which to jar the files.</td>
- <td valign="top" align="center">Yes</td>
+ <td valign="top" align="center">No</td>
</tr>
<tr>
<td valign="top">compress</td>
@@ -2079,6 +2088,11 @@
<td valign="top">the manifest file to use.</td>
<td valign="top" align="center">No</td>
</tr>
+ <tr>
+ <td valign="top">whenempty</td>
+ <td valign="top">Behavior to use if no files match.</td>
+ <td valign="top" align="center">No</td>
+ </tr>
</table>
<h3>Examples</h3>
<pre> <jar jarfile="${dist}/lib/app.jar" basedir="${build}/classes" /></pre>
@@ -2100,6 +2114,21 @@
called <code>app.jar</code> in the <code>${dist}/lib</code> directory. Only
files under the directory <code>mypackage/test</code> are used, and files with
the name <code>Test.class</code> are excluded.</p>
+<pre> <jar jarfile="${dist}/lib/app.jar">
+ <fileset dir="${build}/classes"
+ excludes="**/Test.class"
+ />
+ <fileset dir="${src}/resources"/>
+ </jar></pre>
+<p>jars all files in the <code>${build}/classes</code> directory and also
+in the <code>${src}/resources</code> directory together in a file
+called <code>app.jar</code> in the <code>${dist}/lib</code> directory.
+Files with the name <code>Test.class</code> are excluded.
+If there are files such as <code>${build}/classes/mypackage/MyClass.class</code>
+and <code>${src}/resources/mypackage/image.gif</code>, they will appear
+in the same directory in the JAR (and thus be considered in the same package
+by Java).</p>
+
<hr>
<h2><a name="java">Java</a></h2>
<h3>Description</h3>
@@ -3655,6 +3684,19 @@
<code><include></code>, <code><exclude></code>,
<code><patternset></code> and <code><patternsetref></code>
elements.</p>
+<p>Or, you may place within it nested file sets, or references to file sets.
+In this case <code>basedir</code> is optional; the implicit file set is <em>only used</em>
+if <code>basedir</code> is set. You may use any mixture of the implicit file set
+(with <code>basedir</code> set, and optional attributes like <code>includes</code>
+and optional subelements like <code><include></code>); explicit nested
+<code><fileset></code> elements; and nested <code><filesetref></code>
+elements; so long as at least one fileset total is specified. The ZIP file will
+only reflect the relative paths of files <em>within</em> each fileset.</p>
+<p>The <code>whenempty</code> parameter controls what happens when no files match.
+If <code>skip</code> (the default), the ZIP is not created and a warning is issued.
+If <code>fail</code>, the ZIP is not created and the build is halted with an error.
+If <code>create</code>, an empty ZIP file (explicitly zero entries) is created,
+which should be recognized as such by compliant ZIP manipulation tools.</p>
<h3>Parameters</h3>
<table border="1" cellpadding="2" cellspacing="0">
<tr>
@@ -3670,7 +3712,7 @@
<tr>
<td valign="top">basedir</td>
<td valign="top">the directory from which to zip the files.</td>
- <td align="center" valign="top">Yes</td>
+ <td align="center" valign="top">No</td>
</tr>
<tr>
<td valign="top">compress</td>
@@ -3707,6 +3749,11 @@
("yes"/"no"). Default excludes are used when omitted.</td>
<td valign="top" align="center">No</td>
</tr>
+ <tr>
+ <td valign="top">whenempty</td>
+ <td valign="top">Behavior when no files match.</td>
+ <td valign="top" align="center">No</td>
+ </tr>
</table>
<h3>Examples</h3>
<pre> <zip zipfile="${dist}/manual.zip"
@@ -3729,6 +3776,14 @@
<p>zips all files in the <code>htdocs/manual</code> directory in a file called <code>manual.zip</code>
in the <code>${dist}</code> directory. Only html files under the directory <code>api</code>
are zipped, and files with the name <code>todo.html</code> are excluded.</p>
+<pre> <zip zipfile="${dist}/manual.zip">
+ <fileset dir="htdocs/manual"/>
+ <fileset dir="." includes="ChangeLog.txt"/>
+ </zip></pre>
+<p>zips all files in the <code>htdocs/manual</code> directory in a file called <code>manual.zip</code>
+in the <code>${dist}</code> directory, and also adds the file <code>ChangeLog.txt</code> in the
+current directory. <code>ChangeLog.txt</code> will be added to the top of the ZIP file, just as if
+it had been located at <code>htdocs/manual/ChangeLog.txt</code>.</p>
<hr>
<h2><a name="optionaltasks">Optional tasks</a></h2>
1.2 +2 -0 jakarta-ant/src/main/org/apache/tools/ant/defaultManifest.mf
Index: defaultManifest.mf
===================================================================
RCS file: /home/cvs/jakarta-ant/src/main/org/apache/tools/ant/defaultManifest.mf,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- defaultManifest.mf 2000/01/13 10:41:40 1.1
+++ defaultManifest.mf 2000/08/30 14:57:48 1.2
@@ -1 +1,3 @@
Manifest-Version: 1.0
+Created-By: Ant @VERSION@
+
1.6 +39 -10 jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Jar.java
Index: Jar.java
===================================================================
RCS file: /home/cvs/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Jar.java,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- Jar.java 2000/06/27 11:12:11 1.5
+++ Jar.java 2000/08/30 14:57:48 1.6
@@ -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; i<files.length; i++) {
+ if (files[i].lastModified() > 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);
}
}
}
1.11 +179 -55 jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Zip.java
Index: Zip.java
===================================================================
RCS file: /home/cvs/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Zip.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- Zip.java 2000/08/03 09:13:18 1.10
+++ Zip.java 2000/08/30 14:57:49 1.11
@@ -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!");
- }
-
- DirectoryScanner ds = super.getDirectoryScanner(baseDir);
+ /**
+ * Adds a set of files (nested fileset attribute).
+ */
+ public void addFileset(FileSet set) {
+ filesets.addElement(set);
+ }
- String[] files = ds.getIncludedFiles();
- String[] dirs = ds.getIncludedDirectories();
+ /**
+ * Adds a reference to a set of files (nested filesetref element).
+ */
+ public void addFilesetref(Reference ref) {
+ filesets.addElement(ref);
+ }
- // quick exit if the target is up to date
- boolean upToDate = true;
- for (int i=0; i<files.length && upToDate; i++)
- if (new File(baseDir,files[i]).lastModified() >
- zipFile.lastModified())
- upToDate = false;
- if (upToDate) return;
+ /**
+ * Sets behavior of the task when no files match.
+ * Possible values are: <code>fail</code> (throw an exception
+ * and halt the build); <code>skip</code> (do not create
+ * any archive, but issue a warning); <code>create</code>
+ * (make an archive with no entries).
+ * Default for zip tasks is <code>skip</code>;
+ * for jar tasks, <code>create</code>.
+ */
+ 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;
+ }
- log("Building "+ archiveType +": "+ zipFile.getAbsolutePath());
+ 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!");
- ZipOutputStream zOut = null;
- try {
- zOut = new ZipOutputStream(new FileOutputStream(zipFile));
- if (doCompress) {
- zOut.setMethod(ZipOutputStream.DEFLATED);
+ Vector dss = new Vector ();
+ if (baseDir != null)
+ dss.addElement(getDirectoryScanner(baseDir));
+ for (int i=0; i<filesets.size(); i++) {
+ Object o = filesets.elementAt(i);
+ FileSet fs;
+ if (o instanceof FileSet) {
+ fs = (FileSet) o;
} else {
- zOut.setMethod(ZipOutputStream.STORED);
+ Reference r = (Reference) o;
+ o = r.getReferencedObject(project);
+ if (o instanceof FileSet) {
+ fs = (FileSet) o;
+ } else {
+ throw new BuildException(r.getRefId() + " does not denote a fileset", location);
+ }
}
- initZipOutputStream(zOut);
+ dss.addElement (fs.getDirectoryScanner(project));
+ }
+ FileScanner[] scanners = new FileScanner[dss.size()];
+ dss.copyInto(scanners);
- 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);
- }
+ // quick exit if the target is up to date
+ // can also handle empty archives
+ if (isUpToDate(scanners, zipFile)) return;
- 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();
+ log("Building "+ archiveType +": "+ zipFile.getAbsolutePath());
- // 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";
+ try {
+ ZipOutputStream zOut = new ZipOutputStream(new FileOutputStream(zipFile));
+ try {
+ if (doCompress) {
+ zOut.setMethod(ZipOutputStream.DEFLATED);
+ } else {
+ zOut.setMethod(ZipOutputStream.STORED);
+ }
+ initZipOutputStream(zOut);
+
+ // XXX ideally would also enter includedDirectories to the archive
+ Hashtable parentDirs = new Hashtable();
+
+ 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();
- throw new BuildException(msg, ioe, location);
- } finally {
- if (zOut != null) {
- try {
- // close up
- zOut.close();
- }
- catch (IOException e) {}
+ // delete a bogus ZIP file
+ if (!zipFile.delete()) {
+ msg += " (and the archive is probably corrupt but I could not delete it)";
}
+
+ throw new BuildException(msg, ioe, location);
}
}
@@ -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<files.length; i++) {
+ if (files[i].lastModified() > 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,