Implemented a TarCat Ant task to slurp tars directly into other tars.  This allows us to preserve things like permissions and symlinks on hostile operating systems.

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@174 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build-tools/ant-gwt/build.xml b/build-tools/ant-gwt/build.xml
new file mode 100644
index 0000000..43c0f27
--- /dev/null
+++ b/build-tools/ant-gwt/build.xml
@@ -0,0 +1,34 @@
+<project name="ant-gwt" default="build" basedir=".">
+	<property name="gwt.root" location="../.." />
+	<property name="project.tail" value="build-tools/ant-gwt" />
+	<import file="${gwt.root}/common.ant.xml" />
+
+	<target name="compile" description="Compiles this project">
+		<mkdir dir="${javac.out}" />
+		<gwt.javac>
+			<classpath>
+				<pathelement location="${gwt.tools.lib}/apache/ant-1.6.5.jar" />
+			</classpath>
+		</gwt.javac>
+	</target>
+
+	<target name="build" depends="compile" description="Packages this project into a jar">
+		<mkdir dir="${gwt.build.lib}" />
+		<gwt.jar destfile="${gwt.build.lib}/${ant.project.name}.jar">
+			<fileset dir="src" />
+			<fileset dir="${javac.out}" />
+		</gwt.jar>
+	</target>
+
+	<target name="checkstyle" description="Static analysis of source">
+		<gwt.checkstyle>
+			<fileset dir="src" />
+		</gwt.checkstyle>
+	</target>
+
+	<target name="clean" description="Cleans this project's intermediate and output files">
+		<delete dir="${project.build}" />
+		<delete file="${project.lib}" />
+	</target>
+
+</project>
diff --git a/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/TarCat.java b/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/TarCat.java
new file mode 100644
index 0000000..45f12b9
--- /dev/null
+++ b/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/TarCat.java
@@ -0,0 +1,261 @@
+/*

+ * Copyright 2006 Google Inc.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ * 

+ * http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+package com.google.gwt.ant.taskdefs;

+

+import org.apache.tools.ant.BuildException;

+import org.apache.tools.ant.taskdefs.Tar;

+import org.apache.tools.ant.types.EnumeratedAttribute;

+import org.apache.tools.bzip2.CBZip2InputStream;

+import org.apache.tools.tar.TarEntry;

+import org.apache.tools.tar.TarInputStream;

+import org.apache.tools.tar.TarOutputStream;

+

+import java.io.BufferedInputStream;

+import java.io.File;

+import java.io.FileInputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.util.Vector;

+import java.util.zip.GZIPInputStream;

+

+/**

+ * An extension to the Ant Tar task that supports slurping in other tar files

+ * without loss of information (such as permissions or symlinks). It behaves in

+ * all respects like the basic Tar task, but adds the nested element

+ * &lt;includetar&gt; which declares another tar file whose <i>contents</i>

+ * should be added to the file being created.

+ * 

+ * In addition to preserving permissions and symlinks no matter what the host

+ * operating system is, there are performance advantages to this approach.

+ * Bypassing the file system cuts the disk activity to 50% or less. The

+ * intermediate files normally generated require data the size of the tar itself

+ * to be both written and read, not to mention the overhead of creating the

+ * individual files, which will generally have to be deleted later. Furthurmore,

+ * since the source and target are often zipped, the savings can be well over

+ * 50%.

+ * 

+ * Example use:

+ * 

+ * <pre>

+ * <taskdef name="tar.cat"

+ *     classname="com.google.gwt.ant.taskdefs.TarCat"

+ *     classpath="${gwt.build.lib}/ant-gwt.jar" />

+ * <tar.cat destfile="foo.tar.gz" compression="gzip" longfile="gnu">

+ *   <!-- all normal tar attributes and elements supported -->

+ *   <tarfileset dir="foo/src">

+ *     <include name="*.class" />

+ *   </tarfileset>

+ *   <!-- tar.cat adds the ability to directly slurp in other tar files -->

+ *   <includetar src="bar.tar.gz" compression="gzip" prefix="bar/" />

+ * </tar.cat>

+ * </pre>

+ */

+public class TarCat extends Tar {

+

+  /**

+   * This is a tar file that should be included into a tar operation.

+   */

+  public static class IncludeTar {

+    /**

+     * The compression method to use to access the included tar file.

+     */

+    private UntarCompressionMethod compression = new UntarCompressionMethod();

+

+    /**

+     * An association from a super Tar to a derived TarCat.

+     */

+    private TarFileSet wrapper;

+

+    /**

+     * Constructs a new IncludeTar.

+     * 

+     * @param wrapper the association from a super Tar to a derived TarExt

+     */

+    public IncludeTar(TarFileSet wrapper) {

+      this.wrapper = wrapper;

+    }

+

+    /**

+     * Set decompression algorithm to use; default=none.

+     * 

+     * Allowable values are

+     * <ul>

+     * <li>none - no compression

+     * <li>gzip - Gzip compression

+     * <li>bzip2 - Bzip2 compression

+     * </ul>

+     * 

+     * @param method compression method

+     */

+    public void setCompression(UntarCompressionMethod method) {

+      compression = method;

+    }

+

+    /**

+     * If the prefix attribute is set, all files in the fileset are prefixed

+     * with that path in the archive. optional.

+     * 

+     * @param prefix the path prefix.

+     */

+    public void setPrefix(String prefix) {

+      wrapper.setPrefix(prefix);

+    }

+

+    /**

+     * Set the name/location of a tar file to add to a tar operation.

+     * 

+     * @param tarFile the tar file to read

+     */

+    public void setSrc(File tarFile) {

+      wrapper.setFile(tarFile);

+    }

+  }

+

+  /**

+   * Straight copy from

+   * {@link org.apache.tools.ant.taskdefs.Untar.UntarCompressionMethod} due to

+   * access restrictions.

+   */

+  public static final class UntarCompressionMethod extends EnumeratedAttribute {

+

+    private static final String BZIP2 = "bzip2";

+    private static final String GZIP = "gzip";

+    private static final String NONE = "none";

+

+    public UntarCompressionMethod() {

+      super();

+      setValue(NONE);

+    }

+

+    public String[] getValues() {

+      return new String[] {NONE, GZIP, BZIP2};

+    }

+

+    private InputStream decompress(final File file, final InputStream istream)

+        throws IOException, BuildException {

+      final String value = getValue();

+      if (GZIP.equals(value)) {

+        return new GZIPInputStream(istream);

+      } else {

+        if (BZIP2.equals(value)) {

+          final char[] magic = new char[] {'B', 'Z'};

+          for (int i = 0; i < magic.length; i++) {

+            if (istream.read() != magic[i]) {

+              throw new BuildException("Invalid bz2 file." + file.toString());

+            }

+          }

+          return new CBZip2InputStream(istream);

+        }

+      }

+      return istream;

+    }

+  }

+

+  /**

+   * The set of tars to include in this tar operation.

+   */

+  Vector includeTars = new Vector();

+

+  /**

+   * A set of tarfileset wrappers mapped to includeTars.

+   */

+  Vector includeTarWrappers = new Vector();

+

+  /**

+   * Creates a TarExt task instance.

+   */

+  public TarCat() {

+  }

+

+  /**

+   * Add a new tar to include in this tar operation.

+   */

+  public IncludeTar createIncludeTar() {

+    /*

+     * Create a dummy tarfileset to hold our own includeTars and add it to the

+     * super class. This is how we get the super class to call us back during

+     * execution.

+     */

+    TarFileSet wrapper = super.createTarFileSet();

+    IncludeTar includeTar = new IncludeTar(wrapper);

+    includeTars.addElement(includeTar);

+    includeTarWrappers.add(wrapper);

+    return includeTar;

+  }

+

+  protected void tarFile(File file, TarOutputStream tOut, String vPath,

+      TarFileSet tarFileSet) throws IOException {

+    // See if it's one of ours

+    int index = includeTarWrappers.indexOf(tarFileSet);

+    if (index < 0) {

+      super.tarFile(file, tOut, vPath, tarFileSet);

+      return;

+    }

+    IncludeTar includeTar = (IncludeTar) includeTars.get(index);

+    TarInputStream tIn = null;

+    try {

+      tIn = new TarInputStream(includeTar.compression.decompress(file,

+          new BufferedInputStream(new FileInputStream(file))));

+      TarEntry te = null;

+      while ((te = tIn.getNextEntry()) != null) {

+        vPath = te.getName();

+

+        // don't add "" to the archive

+        if (vPath.length() <= 0) {

+          continue;

+        }

+

+        if (te.isDirectory() && !vPath.endsWith("/")) {

+          vPath += "/";

+        }

+

+        String prefix = tarFileSet.getPrefix();

+        // '/' is appended for compatibility with the zip task.

+        if (prefix.length() > 0 && !prefix.endsWith("/")) {

+          prefix = prefix + "/";

+        }

+        vPath = prefix + vPath;

+

+        te.setName(vPath);

+        tOut.putNextEntry(te);

+

+        if (te.getSize() > 0) {

+          byte[] buffer = new byte[8 * 1024];

+          while (true) {

+            int count = tIn.read(buffer, 0, buffer.length);

+            if (count < 0) {

+              break;

+            }

+            tOut.write(buffer, 0, count);

+          }

+        }

+        tOut.closeEntry();

+      }

+

+    } catch (IOException ioe) {

+      throw new BuildException("Error while expanding " + file.getPath(), ioe,

+          getLocation());

+    } finally {

+      if (tIn != null) {

+        try {

+          tIn.close();

+        } catch (IOException e) {

+          // ignore

+        }

+      }

+    }

+  }

+}

diff --git a/build-tools/build.xml b/build-tools/build.xml
index 7846150..e16d42c 100644
--- a/build-tools/build.xml
+++ b/build-tools/build.xml
@@ -6,6 +6,10 @@
 	<!-- "build" is the default when subprojects are directly targetted -->
 	<property name="target" value="build" />
 
+	<target name="ant-gwt" description="Builds GWT specific Ant extensions">
+		<gwt.ant dir="ant-gwt" />
+	</target>
+
 	<target name="customchecks" description="Build the checkstyle extensions">
 		<gwt.ant dir="customchecks" />
 	</target>
@@ -14,7 +18,7 @@
 		<gwt.ant dir="doctool" />
 	</target>
 
-	<target name="-do" depends="customchecks, doctool" description="Run all subprojects"/>
+	<target name="-do" depends="ant-gwt, customchecks, doctool" description="Run all subprojects"/>
 	
 	<target name="build" description="Builds GWT">
 		<antcall target="-do">
diff --git a/common.ant.xml b/common.ant.xml
index c01d278..cddd93f 100755
--- a/common.ant.xml
+++ b/common.ant.xml
@@ -143,17 +143,14 @@
 		</sequential>
 	</macrodef>
 
-	<macrodef name="gwt.untar">
-		<attribute name="src" />
-		<attribute name="dest" />
+	<macrodef name="gwt.tgz.cat">
+		<attribute name="destfile" />
+		<element name="tar.elements" implicit="true" optional="true" />
 		<sequential>
-			<!-- GNU tar handles permissions and symlinks correctly -->
-			<exec executable="tar" failonerror="true">
-				<arg value="-xpzf" />
-				<arg file="@{src}" />
-				<arg value="-C" />
-				<arg file="@{dest}" />
-			</exec>
+			<taskdef name="tar.cat" classname="com.google.gwt.ant.taskdefs.TarCat" classpath="${gwt.build.lib}/ant-gwt.jar" />
+			<tar.cat destfile="${project.dist}" compression="gzip" longfile="gnu">
+				<tar.elements />
+			</tar.cat>
 		</sequential>
 	</macrodef>
 
diff --git a/distro-source/common.ant.xml b/distro-source/common.ant.xml
index 5f344c9..082fde8 100755
--- a/distro-source/common.ant.xml
+++ b/distro-source/common.ant.xml
@@ -6,6 +6,18 @@
 	<property name="project.distname" value="gwt-${dist.platform}-${gwt.version}" />
 	<property name="project.staging" location="${gwt.build.staging}/${project.distname}" />
 
+	<patternset id="chmod.executables">
+		<include name="*Creator*" />
+		<include name="samples/*/*-shell" />
+		<include name="samples/*/*-compile" />
+	</patternset>
+
+	<patternset id="chmod.not.executables">
+		<exclude name="*Creator*" />
+		<exclude name="samples/*/*-shell*" />
+		<exclude name="samples/*/*-compile*" />
+	</patternset>
+
 	<!-- copies a single sample into the staging directory -->
 	<macrodef name="stage.sample">
 		<attribute name="lname" />
@@ -24,12 +36,6 @@
 					<include name="*" />
 				</fileset>
 			</copy>
-			<chmod perm="a+x">
-				<fileset dir="${project.staging}/samples/@{uname}">
-					<include name="@{uname}-compile*" />
-					<include name="@{uname}-shell*" />
-				</fileset>
-			</chmod>
 		</sequential>
 	</macrodef>
 
@@ -69,14 +75,9 @@
 		<stage.sample lname="simplexml" uname="SimpleXML" />
 
 		<antcall target="stage.platform" />
-		<chmod perm="a+r">
-			<dirset dir="${project.staging}" />
-			<fileset dir="${project.staging}" />
-		</chmod>
-		<chmod perm="a+x">
-			<dirset dir="${project.staging}" />
+		<chmod perm="755">
 			<fileset dir="${project.staging}">
-				<include name="*Creator*" />
+				<patternset refid="chmod.executables" />
 			</fileset>
 		</chmod>
 	</target>
diff --git a/distro-source/linux/build.xml b/distro-source/linux/build.xml
index cefc2cb..27e4773 100755
--- a/distro-source/linux/build.xml
+++ b/distro-source/linux/build.xml
@@ -4,7 +4,7 @@
 	<property name="project.dist" location="${gwt.build.dist}/${project.distname}.tar.gz" />
 
 	<target name="stage.platform" description="Copies platform-specific items into the staging area">
-		<gwt.untar src="${gwt.tools.redist}/mozilla/mozilla-1.7.12.tar.gz" dest="${project.staging}" />
+		<untar src="${gwt.tools.redist}/mozilla/mozilla-1.7.12.tar.gz" dest="${project.staging}" compression="gzip" />
 		<copy todir="${project.staging}">
 			<fileset dir="${gwt.tools.lib}/eclipse">
 				<include name="libswt-*gtk-3235.so" />
@@ -14,13 +14,18 @@
 
 	<target name="build" depends="stage" description="Packages the distro staging area">
 		<mkdir dir="${gwt.build.dist}" />
-		<!-- GNU tar handles permissions and symlinks correctly -->
-		<exec executable="tar" failonerror="true">
-			<arg value="-cpzf" />
-			<arg value="${project.dist}" />
-			<arg value="-C" />
-			<arg file="${gwt.build.staging}" />
-			<arg value="${project.distname}" />
-		</exec>
+		<gwt.tgz.cat destfile="${project.dist}">
+			<tarfileset dir="${gwt.build.staging}/${project.distname}" prefix="${project.distname}">
+				<!-- Mozilla pulled in through includetar -->
+				<exclude name="mozilla-1.7.12/**" />
+				<patternset refid="chmod.not.executables" />
+			</tarfileset>
+			<tarfileset dir="${gwt.build.staging}/${project.distname}" prefix="${project.distname}" mode="755">
+				<!-- Mozilla pulled in through includetar -->
+				<exclude name="mozilla-1.7.12/**" />
+				<patternset refid="chmod.executables" />
+			</tarfileset>
+			<includetar src="${gwt.tools.redist}/mozilla/mozilla-1.7.12.tar.gz" compression="gzip" prefix="${project.distname}" />
+		</gwt.tgz.cat>
 	</target>
 </project>
diff --git a/distro-source/mac/build.xml b/distro-source/mac/build.xml
index 4c9d901..cb4e146 100755
--- a/distro-source/mac/build.xml
+++ b/distro-source/mac/build.xml
@@ -4,7 +4,16 @@
 	<property name="project.dist" location="${gwt.build.dist}/${project.distname}.tar.gz" />
 
 	<target name="stage.platform" description="Copies platform-specific items into the staging area">
-		<gwt.untar src="${gwt.tools.redist}/webkit/WebKit-418.9.tar.gz" dest="${project.staging}" />
+		<!--
+		 	Try to untar WebKit into the staging directory.  GNU tar handles
+			permissions and symlinks correctly.  It's okay if we fail here.
+		-->
+		<exec executable="tar" failifexecutionfails="false" failonerror="false">
+			<arg value="-xpzf" />
+			<arg file="${gwt.tools.redist}/webkit/WebKit-418.9.tar.gz" />
+			<arg value="-C" />
+			<arg file="${project.staging}" />
+		</exec>
 		<copy todir="${project.staging}">
 			<fileset dir="${gwt.tools.lib}/eclipse">
 				<include name="libswt-*carbon-3235.jnilib" />
@@ -16,13 +25,18 @@
 
 	<target name="build" depends="stage" description="Packages the distro staging area">
 		<mkdir dir="${gwt.build.dist}" />
-		<!-- GNU tar handles permissions and symlinks correctly -->
-		<exec executable="tar" failonerror="true">
-			<arg value="-cpzf" />
-			<arg value="${project.dist}" />
-			<arg value="-C" />
-			<arg file="${gwt.build.staging}" />
-			<arg value="${project.distname}" />
-		</exec>
+		<gwt.tgz.cat destfile="${project.dist}">
+			<tarfileset dir="${gwt.build.staging}/${project.distname}" prefix="${project.distname}">
+				<!-- Frameworks pulled in through includetar -->
+				<exclude name="Frameworks/**" />
+				<patternset refid="chmod.not.executables" />
+			</tarfileset>
+			<tarfileset dir="${gwt.build.staging}/${project.distname}" prefix="${project.distname}" mode="755">
+				<!-- Frameworks pulled in through includetar -->
+				<exclude name="Frameworks/**" />
+				<patternset refid="chmod.executables" />
+			</tarfileset>
+			<includetar src="${gwt.tools.redist}/webkit/WebKit-418.9.tar.gz" compression="gzip" prefix="${project.distname}" />
+		</gwt.tgz.cat>
 	</target>
 </project>
diff --git a/distro-source/windows/build.xml b/distro-source/windows/build.xml
index 38361a3..5bc73f7 100755
--- a/distro-source/windows/build.xml
+++ b/distro-source/windows/build.xml
@@ -14,9 +14,7 @@
 	<target name="build" depends="stage" description="Packages the distro staging area">
 		<mkdir dir="${gwt.build.dist}" />
 		<zip destfile="${project.dist}">
-			<fileset dir="${gwt.build.staging}">
-				<include name="${project.distname}/**" />
-			</fileset>
+			<zipfileset dir="${gwt.build.staging}/${project.distname}" prefix="${project.distname}" />
 		</zip>
 	</target>
 </project>
diff --git a/eclipse/build-tools/ant-gwt/.checkstyle b/eclipse/build-tools/ant-gwt/.checkstyle
new file mode 100644
index 0000000..afcc9b2
--- /dev/null
+++ b/eclipse/build-tools/ant-gwt/.checkstyle
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<fileset-config file-format-version="1.2.0" simple-config="false">

+    <fileset name="all" enabled="true" check-config-name="GWT Checks" local="false">

+        <file-match-pattern match-pattern="." include-pattern="true"/>

+    </fileset>

+</fileset-config>

diff --git a/eclipse/build-tools/ant-gwt/.classpath b/eclipse/build-tools/ant-gwt/.classpath
new file mode 100644
index 0000000..727a3f7
--- /dev/null
+++ b/eclipse/build-tools/ant-gwt/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<classpath>

+	<classpathentry kind="src" path="core/src"/>

+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>

+	<classpathentry kind="var" path="GWT_TOOLS/lib/apache/ant-1.6.5.jar" sourcepath="/GWT_TOOLS/lib/apache/ant-1.6.5-src.zip"/>

+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/ant-launcher-1.6.5.jar"/>

+	<classpathentry kind="output" path="bin"/>

+</classpath>

diff --git a/eclipse/build-tools/ant-gwt/.project b/eclipse/build-tools/ant-gwt/.project
new file mode 100644
index 0000000..5a5195a
--- /dev/null
+++ b/eclipse/build-tools/ant-gwt/.project
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<projectDescription>

+	<name>ant-gwt</name>

+	<comment></comment>

+	<projects>

+	</projects>

+	<buildSpec>

+		<buildCommand>

+			<name>org.eclipse.jdt.core.javabuilder</name>

+			<arguments>

+			</arguments>

+		</buildCommand>

+		<buildCommand>

+			<name>com.atlassw.tools.eclipse.checkstyle.CheckstyleBuilder</name>

+			<arguments>

+			</arguments>

+		</buildCommand>

+	</buildSpec>

+	<natures>

+		<nature>org.eclipse.jdt.core.javanature</nature>

+		<nature>com.atlassw.tools.eclipse.checkstyle.CheckstyleNature</nature>

+	</natures>

+	<linkedResources>

+		<link>

+			<name>core</name>

+			<type>2</type>

+			<location>GWT_ROOT/build-tools/ant-gwt</location>

+		</link>

+	</linkedResources>

+</projectDescription>