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>&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 @@
     <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>  &lt;jar jarfile=&quot;${dist}/lib/app.jar&quot; basedir=&quot;${build}/classes&quot; /&gt;</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>  &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 @@
   <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 @@
     <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 @@
         (&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 @@
   <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>
  
  
  
  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,