Browse Source

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 <Jesse.Glick@netbeans.com>


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@267947 13f79535-47bb-0310-9956-ffa450edef68
master
Conor MacNeill 25 years ago
parent
commit
7ac63c0bc2
5 changed files with 277 additions and 67 deletions
  1. +1
    -1
      build.xml
  2. +57
    -2
      docs/index.html
  3. +2
    -0
      src/main/org/apache/tools/ant/defaultManifest.mf
  4. +39
    -10
      src/main/org/apache/tools/ant/taskdefs/Jar.java
  5. +178
    -54
      src/main/org/apache/tools/ant/taskdefs/Zip.java

+ 1
- 1
build.xml View File

@@ -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>



+ 57
- 2
docs/index.html View File

@@ -2022,6 +2022,15 @@ supports all attributes of <code>&lt;fileset&gt;</code>
<code>&lt;include&gt;</code>, <code>&lt;exclude&gt;</code>,
<code>&lt;patternset&gt;</code> and <code>&lt;patternsetref&gt;</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 @@ elements.</p>
<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 @@ elements.</p>
<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> &lt;jar jarfile=&quot;${dist}/lib/app.jar&quot; basedir=&quot;${build}/classes&quot; /&gt;</pre>
@@ -2100,6 +2114,21 @@ with the name <code>Test.class</code> are excluded.</p>
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> &lt;jar jarfile=&quot;${dist}/lib/app.jar&quot;&gt;
&lt;fileset dir=&quot;${build}/classes&quot;
excludes=&quot;**/Test.class&quot;
/&gt;
&lt;fileset dir=&quot;${src}/resources&quot;/&gt;
&lt;/jar&gt;</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 @@ supports all attributes of <code>&lt;fileset&gt;</code>
<code>&lt;include&gt;</code>, <code>&lt;exclude&gt;</code>,
<code>&lt;patternset&gt;</code> and <code>&lt;patternsetref&gt;</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>&lt;include&gt;</code>); explicit nested
<code>&lt;fileset&gt;</code> elements; and nested <code>&lt;filesetref&gt;</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 @@ elements.</p>
<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 @@ elements.</p>
(&quot;yes&quot;/&quot;no&quot;). 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> &lt;zip zipfile=&quot;${dist}/manual.zip&quot;
@@ -3729,6 +3776,14 @@ or files with the name <code>todo.html</code> are excluded.</p>
<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> &lt;zip zipfile=&quot;${dist}/manual.zip&quot;&gt;
&lt;fileset dir=&quot;htdocs/manual&quot;/&gt;
&lt;fileset dir=&quot;.&quot; includes=&quot;ChangeLog.txt&quot;/&gt;
&lt;/zip&gt;</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>


+ 2
- 0
src/main/org/apache/tools/ant/defaultManifest.mf View File

@@ -1 +1,3 @@
Manifest-Version: 1.0
Created-By: Ant @VERSION@


+ 39
- 10
src/main/org/apache/tools/ant/taskdefs/Jar.java View File

@@ -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<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);
}
}
}

+ 178
- 54
src/main/org/apache/tools/ant/taskdefs/Zip.java View File

@@ -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: <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;
}

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<filesets.size(); i++) {
Object o = filesets.elementAt(i);
FileSet fs;
if (o instanceof FileSet) {
fs = (FileSet) o;
} else {
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);
}
}
dss.addElement (fs.getDirectoryScanner(project));
}
FileScanner[] scanners = new FileScanner[dss.size()];
dss.copyInto(scanners);

// 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;
// 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<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,


Loading…
Cancel
Save