/* * Copyright 2008 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.IdentityHashMap; import java.util.Map; 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 * {@code <includetar>} 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(); /** * This instance's peer object that is a member of {@link TarCat}'s * superclass. */ private TarFileSet peer; /** * Constructs a new IncludeTar. * * @param peer this instance's peer object that is a member of * {@link TarCat}'s superclass */ public IncludeTar(TarFileSet peer) { this.peer = peer; } /** * 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) { peer.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) { peer.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; } } /** * A map to the set of {@link IncludeTar}s from their peer objects. */ private final Map<TarFileSet, IncludeTar> peerMap = new IdentityHashMap<TarFileSet, IncludeTar>(); /** * Creates a task instance. */ public TarCat() { } /** * Add a new tar to include in this tar operation. */ public IncludeTar createIncludeTar() { /* * Create a peer TarFileSet corresponding to one of our own IncludeTars. * This causes the superclass to call us back during execution. */ TarFileSet peer = super.createTarFileSet(); IncludeTar includeTar = new IncludeTar(peer); peerMap.put(peer, includeTar); return includeTar; } protected void tarFile(File file, TarOutputStream tOut, String vPath, TarFileSet tarFileSet) throws IOException { if (!peerMap.containsKey(tarFileSet)) { // Not one of ours, punt to superclass. super.tarFile(file, tOut, vPath, tarFileSet); return; } IncludeTar includeTar = peerMap.get(tarFileSet); String prefix = tarFileSet.getPrefix(); if (prefix.length() > 0 && !prefix.endsWith("/")) { // '/' is appended for compatibility with the zip task. prefix = prefix + "/"; } 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 += "/"; } 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 e) { throw new BuildException("Error while expanding " + file.getPath(), e, getLocation()); } finally { if (tIn != null) { try { tIn.close(); } catch (IOException ignored) { } } } } }