| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 |
| * |
| * https://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 org.apache.tools.ant.taskdefs; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.nio.file.Files; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Vector; |
| import java.util.zip.GZIPOutputStream; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.types.ArchiveFileSet; |
| import org.apache.tools.ant.types.EnumeratedAttribute; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.types.Resource; |
| import org.apache.tools.ant.types.ResourceCollection; |
| import org.apache.tools.ant.types.resources.ArchiveResource; |
| import org.apache.tools.ant.types.resources.FileProvider; |
| import org.apache.tools.ant.types.resources.FileResource; |
| import org.apache.tools.ant.types.resources.TarResource; |
| import org.apache.tools.ant.types.selectors.SelectorUtils; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.MergingMapper; |
| import org.apache.tools.ant.util.ResourceUtils; |
| import org.apache.tools.ant.util.SourceFileScanner; |
| import org.apache.tools.bzip2.CBZip2OutputStream; |
| import org.apache.tools.tar.TarConstants; |
| import org.apache.tools.tar.TarEntry; |
| import org.apache.tools.tar.TarOutputStream; |
| |
| /** |
| * Creates a tar archive. |
| * |
| * @since Ant 1.1 |
| * |
| * @ant.task category="packaging" |
| */ |
| public class Tar extends MatchingTask { |
| private static final int BUFFER_SIZE = 8 * 1024; |
| |
| /** |
| * @deprecated since 1.5.x. |
| * Tar.WARN is deprecated and is replaced with |
| * Tar.TarLongFileMode.WARN |
| */ |
| @Deprecated |
| public static final String WARN = "warn"; |
| /** |
| * @deprecated since 1.5.x. |
| * Tar.FAIL is deprecated and is replaced with |
| * Tar.TarLongFileMode.FAIL |
| */ |
| @Deprecated |
| public static final String FAIL = "fail"; |
| /** |
| * @deprecated since 1.5.x. |
| * Tar.TRUNCATE is deprecated and is replaced with |
| * Tar.TarLongFileMode.TRUNCATE |
| */ |
| @Deprecated |
| public static final String TRUNCATE = "truncate"; |
| /** |
| * @deprecated since 1.5.x. |
| * Tar.GNU is deprecated and is replaced with |
| * Tar.TarLongFileMode.GNU |
| */ |
| @Deprecated |
| public static final String GNU = "gnu"; |
| /** |
| * @deprecated since 1.5.x. |
| * Tar.OMIT is deprecated and is replaced with |
| * Tar.TarLongFileMode.OMIT |
| */ |
| @Deprecated |
| public static final String OMIT = "omit"; |
| |
| // CheckStyle:VisibilityModifier OFF - bc |
| File tarFile; |
| File baseDir; |
| |
| private TarLongFileMode longFileMode = new TarLongFileMode(); |
| |
| // need to keep the package private version for backwards compatibility |
| Vector<TarFileSet> filesets = new Vector<>(); |
| // we must keep two lists since other classes may modify the |
| // filesets Vector (it is package private) without us noticing |
| private final List<ResourceCollection> resourceCollections = new Vector<>(); |
| |
| // CheckStyle:VisibilityModifier ON |
| |
| /** |
| * Indicates whether the user has been warned about long files already. |
| */ |
| private boolean longWarningGiven = false; |
| |
| private TarCompressionMethod compression = new TarCompressionMethod(); |
| |
| /** |
| * Encoding to use for filenames, defaults to the platform's |
| * default encoding. |
| */ |
| private String encoding; |
| |
| /** |
| * Add a new fileset with the option to specify permissions |
| * @return the tar fileset to be used as the nested element. |
| */ |
| public TarFileSet createTarFileSet() { |
| final TarFileSet fs = new TarFileSet(); |
| fs.setProject(getProject()); |
| filesets.addElement(fs); |
| return fs; |
| } |
| |
| /** |
| * Add a collection of resources to archive. |
| * @param res a resource collection to archive. |
| * @since Ant 1.7 |
| */ |
| public void add(final ResourceCollection res) { |
| resourceCollections.add(res); |
| } |
| |
| /** |
| * Set is the name/location of where to create the tar file. |
| * @param tarFile the location of the tar file. |
| * @deprecated since 1.5.x. |
| * For consistency with other tasks, please use setDestFile(). |
| */ |
| @Deprecated |
| public void setTarfile(final File tarFile) { |
| this.tarFile = tarFile; |
| } |
| |
| /** |
| * Set is the name/location of where to create the tar file. |
| * @since Ant 1.5 |
| * @param destFile The output of the tar |
| */ |
| public void setDestFile(final File destFile) { |
| this.tarFile = destFile; |
| } |
| |
| /** |
| * This is the base directory to look in for things to tar. |
| * @param baseDir the base directory. |
| */ |
| public void setBasedir(final File baseDir) { |
| this.baseDir = baseDir; |
| } |
| |
| /** |
| * Set how to handle long files, those with a path>100 chars. |
| * Optional, default=warn. |
| * <p> |
| * Allowable values are |
| * <ul> |
| * <li> truncate - paths are truncated to the maximum length |
| * <li> fail - paths greater than the maximum cause a build exception |
| * <li> warn - paths greater than the maximum cause a warning and GNU is used |
| * <li> gnu - GNU extensions are used for any paths greater than the maximum. |
| * <li> omit - paths greater than the maximum are omitted from the archive |
| * </ul> |
| * @param mode the mode string to handle long files. |
| * @deprecated since 1.5.x. |
| * setLongFile(String) is deprecated and is replaced with |
| * setLongFile(Tar.TarLongFileMode) to make Ant's Introspection |
| * mechanism do the work and also to encapsulate operations on |
| * the mode in its own class. |
| */ |
| @Deprecated |
| public void setLongfile(final String mode) { |
| log("DEPRECATED - The setLongfile(String) method has been deprecated. Use setLongfile(Tar.TarLongFileMode) instead."); |
| this.longFileMode = new TarLongFileMode(); |
| longFileMode.setValue(mode); |
| } |
| |
| /** |
| * Set how to handle long files, those with a path>100 chars. |
| * Optional, default=warn. |
| * <p> |
| * Allowable values are |
| * <ul> |
| * <li> truncate - paths are truncated to the maximum length |
| * <li> fail - paths greater than the maximum cause a build exception |
| * <li> warn - paths greater than the maximum cause a warning and GNU is used |
| * <li> gnu - extensions used by older versions of GNU tar are used for any paths greater than the maximum. |
| * <li> posix - use POSIX PAX extension headers for any paths greater than the maximum. Supported by all modern tar implementations. |
| * <li> omit - paths greater than the maximum are omitted from the archive |
| * </ul> |
| * @param mode the mode to handle long file names. |
| */ |
| public void setLongfile(final TarLongFileMode mode) { |
| this.longFileMode = mode; |
| } |
| |
| /** |
| * Set compression method. |
| * Allowable values are |
| * <ul> |
| * <li> none - no compression |
| * <li> gzip - Gzip compression |
| * <li> bzip2 - Bzip2 compression |
| * <li>xz - XZ compression, requires XZ for Java |
| * </ul> |
| * @param mode the compression method. |
| */ |
| public void setCompression(final TarCompressionMethod mode) { |
| this.compression = mode; |
| } |
| |
| /** |
| * Encoding to use for filenames, defaults to the platform's |
| * default encoding. |
| * |
| * <p>For a list of possible values see <a |
| * href="https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html">https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html</a>.</p> |
| * @param encoding the encoding name |
| * |
| * @since Ant 1.9.5 |
| */ |
| public void setEncoding(final String encoding) { |
| this.encoding = encoding; |
| } |
| |
| /** |
| * do the business |
| * @throws BuildException on error |
| */ |
| @Override |
| public void execute() throws BuildException { |
| if (tarFile == null) { |
| throw new BuildException("tarfile attribute must be set!", |
| getLocation()); |
| } |
| |
| if (tarFile.exists() && tarFile.isDirectory()) { |
| throw new BuildException("tarfile is a directory!", |
| getLocation()); |
| } |
| |
| if (tarFile.exists() && !tarFile.canWrite()) { |
| throw new BuildException("Can not write to the specified tarfile!", |
| getLocation()); |
| } |
| |
| final Vector<TarFileSet> savedFileSets = new Vector<>(filesets); |
| try { |
| if (baseDir != null) { |
| if (!baseDir.exists()) { |
| throw new BuildException("basedir does not exist!", |
| getLocation()); |
| } |
| |
| // add the main fileset to the list of filesets to process. |
| final TarFileSet mainFileSet = new TarFileSet(fileset); |
| mainFileSet.setDir(baseDir); |
| filesets.addElement(mainFileSet); |
| } |
| |
| if (filesets.isEmpty() && resourceCollections.isEmpty()) { |
| throw new BuildException( |
| "You must supply either a basedir attribute or some nested resource collections.", |
| getLocation()); |
| } |
| |
| // check if tar is out of date with respect to each |
| // fileset |
| boolean upToDate = true; |
| for (final TarFileSet tfs : filesets) { |
| upToDate &= check(tfs); |
| } |
| for (final ResourceCollection rcol : resourceCollections) { |
| upToDate &= check(rcol); |
| } |
| |
| if (upToDate) { |
| log("Nothing to do: " + tarFile.getAbsolutePath() |
| + " is up to date.", Project.MSG_INFO); |
| return; |
| } |
| |
| final File parent = tarFile.getParentFile(); |
| if (parent != null && !parent.isDirectory() |
| && !(parent.mkdirs() || parent.isDirectory())) { |
| throw new BuildException( |
| "Failed to create missing parent directory for %s", |
| tarFile); |
| } |
| |
| log("Building tar: " + tarFile.getAbsolutePath(), Project.MSG_INFO); |
| |
| try (TarOutputStream tOut = new TarOutputStream( |
| compression.compress(new BufferedOutputStream( |
| Files.newOutputStream(tarFile.toPath()))), |
| encoding)) { |
| tOut.setDebug(true); |
| if (longFileMode.isTruncateMode()) { |
| tOut.setLongFileMode(TarOutputStream.LONGFILE_TRUNCATE); |
| } else if (longFileMode.isFailMode() |
| || longFileMode.isOmitMode()) { |
| tOut.setLongFileMode(TarOutputStream.LONGFILE_ERROR); |
| } else if (longFileMode.isPosixMode()) { |
| tOut.setLongFileMode(TarOutputStream.LONGFILE_POSIX); |
| } else { |
| // warn or GNU |
| tOut.setLongFileMode(TarOutputStream.LONGFILE_GNU); |
| } |
| |
| longWarningGiven = false; |
| for (final TarFileSet tfs : filesets) { |
| tar(tfs, tOut); |
| } |
| for (final ResourceCollection rcol : resourceCollections) { |
| tar(rcol, tOut); |
| } |
| } catch (final IOException ioe) { |
| final String msg = "Problem creating TAR: " + ioe.getMessage(); |
| throw new BuildException(msg, ioe, getLocation()); |
| } |
| } finally { |
| filesets = savedFileSets; |
| } |
| } |
| |
| /** |
| * tar a file |
| * @param file the file to tar |
| * @param tOut the output stream |
| * @param vPath the path name of the file to tar |
| * @param tarFileSet the fileset that the file came from. |
| * @throws IOException on error |
| */ |
| protected void tarFile(final File file, final TarOutputStream tOut, final String vPath, |
| final TarFileSet tarFileSet) |
| throws IOException { |
| if (file.equals(tarFile)) { |
| // If the archive is built for the first time and it is |
| // matched by a resource collection, then it hasn't been |
| // found in check (it hasn't been there) but will be |
| // included now. |
| // |
| // for some strange reason the old code would simply skip |
| // the entry and not fail, do the same now for backwards |
| // compatibility reasons. Without this, the which4j build |
| // fails in Gump |
| return; |
| } |
| tarResource(new FileResource(file), tOut, vPath, tarFileSet); |
| } |
| |
| /** |
| * tar a resource |
| * @param r the resource to tar |
| * @param tOut the output stream |
| * @param vPath the path name of the file to tar |
| * @param tarFileSet the fileset that the file came from, may be null. |
| * @throws IOException on error |
| * @since Ant 1.7 |
| */ |
| protected void tarResource(final Resource r, final TarOutputStream tOut, String vPath, |
| final TarFileSet tarFileSet) |
| throws IOException { |
| |
| if (!r.isExists()) { |
| return; |
| } |
| |
| boolean preserveLeadingSlashes = false; |
| |
| if (tarFileSet != null) { |
| final String fullpath = tarFileSet.getFullpath(this.getProject()); |
| if (fullpath.isEmpty()) { |
| // don't add "" to the archive |
| if (vPath.isEmpty()) { |
| return; |
| } |
| |
| String prefix = tarFileSet.getPrefix(this.getProject()); |
| // '/' is appended for compatibility with the zip task. |
| if (!prefix.isEmpty() && !prefix.endsWith("/")) { |
| prefix += "/"; |
| } |
| vPath = prefix + vPath; |
| } else { |
| vPath = fullpath; |
| } |
| |
| preserveLeadingSlashes = tarFileSet.getPreserveLeadingSlashes(); |
| |
| if (vPath.startsWith("/") && !preserveLeadingSlashes) { |
| final int l = vPath.length(); |
| if (l <= 1) { |
| // we would end up adding "" to the archive |
| return; |
| } |
| vPath = vPath.substring(1, l); |
| } |
| } |
| |
| if (r.isDirectory() && !vPath.endsWith("/")) { |
| vPath += "/"; |
| } |
| |
| if (vPath.length() >= TarConstants.NAMELEN) { |
| if (longFileMode.isOmitMode()) { |
| log("Omitting: " + vPath, Project.MSG_INFO); |
| return; |
| } else if (longFileMode.isWarnMode()) { |
| log("Entry: " + vPath + " longer than " |
| + TarConstants.NAMELEN + " characters.", |
| Project.MSG_WARN); |
| if (!longWarningGiven) { |
| log("Resulting tar file can only be processed " |
| + "successfully by GNU compatible tar commands", |
| Project.MSG_WARN); |
| longWarningGiven = true; |
| } |
| } else if (longFileMode.isFailMode()) { |
| throw new BuildException("Entry: " + vPath |
| + " longer than " + TarConstants.NAMELEN |
| + "characters.", getLocation()); |
| } |
| } |
| |
| final TarEntry te = new TarEntry(vPath, preserveLeadingSlashes); |
| te.setModTime(r.getLastModified()); |
| // preserve permissions |
| if (r instanceof ArchiveResource) { |
| final ArchiveResource ar = (ArchiveResource) r; |
| te.setMode(ar.getMode()); |
| if (r instanceof TarResource) { |
| final TarResource tr = (TarResource) r; |
| te.setUserName(tr.getUserName()); |
| te.setUserId(tr.getLongUid()); |
| te.setGroupName(tr.getGroup()); |
| te.setGroupId(tr.getLongGid()); |
| } |
| } |
| |
| if (!r.isDirectory()) { |
| if (r.size() > TarConstants.MAXSIZE) { |
| throw new BuildException( |
| "Resource: " + r + " larger than " |
| + TarConstants.MAXSIZE + " bytes."); |
| } |
| te.setSize(r.getSize()); |
| // override permissions if set explicitly |
| if (tarFileSet != null && tarFileSet.hasFileModeBeenSet()) { |
| te.setMode(tarFileSet.getMode()); |
| } |
| } else if (tarFileSet != null && tarFileSet.hasDirModeBeenSet()) { |
| // override permissions if set explicitly |
| te.setMode(tarFileSet.getDirMode(this.getProject())); |
| } |
| |
| if (tarFileSet != null) { |
| // only override permissions if set explicitly |
| if (tarFileSet.hasUserNameBeenSet()) { |
| te.setUserName(tarFileSet.getUserName()); |
| } |
| if (tarFileSet.hasGroupBeenSet()) { |
| te.setGroupName(tarFileSet.getGroup()); |
| } |
| if (tarFileSet.hasUserIdBeenSet()) { |
| te.setUserId(tarFileSet.getUid()); |
| } |
| if (tarFileSet.hasGroupIdBeenSet()) { |
| te.setGroupId(tarFileSet.getGid()); |
| } |
| } |
| |
| InputStream in = null; |
| try { |
| tOut.putNextEntry(te); |
| |
| if (!r.isDirectory()) { |
| in = r.getInputStream(); |
| |
| final byte[] buffer = new byte[BUFFER_SIZE]; |
| int count = 0; |
| do { |
| tOut.write(buffer, 0, count); |
| count = in.read(buffer, 0, buffer.length); |
| } while (count != -1); |
| } |
| |
| tOut.closeEntry(); |
| } finally { |
| FileUtils.close(in); |
| } |
| } |
| |
| /** |
| * Is the archive up to date in relationship to a list of files. |
| * @param files the files to check |
| * @return true if the archive is up to date. |
| * @deprecated since 1.5.x. |
| * use the two-arg version instead. |
| */ |
| @Deprecated |
| protected boolean archiveIsUpToDate(final String[] files) { |
| return archiveIsUpToDate(files, baseDir); |
| } |
| |
| /** |
| * Is the archive up to date in relationship to a list of files. |
| * @param files the files to check |
| * @param dir the base directory for the files. |
| * @return true if the archive is up to date. |
| * @since Ant 1.5.2 |
| */ |
| protected boolean archiveIsUpToDate(final String[] files, final File dir) { |
| final SourceFileScanner sfs = new SourceFileScanner(this); |
| final MergingMapper mm = new MergingMapper(); |
| mm.setTo(tarFile.getAbsolutePath()); |
| return sfs.restrict(files, dir, null, mm).length == 0; |
| } |
| |
| /** |
| * Is the archive up to date in relationship to a list of files. |
| * @param r the files to check |
| * @return true if the archive is up to date. |
| * @since Ant 1.7 |
| */ |
| protected boolean archiveIsUpToDate(final Resource r) { |
| return SelectorUtils.isOutOfDate(new FileResource(tarFile), r, |
| FileUtils.getFileUtils() |
| .getFileTimestampGranularity()); |
| } |
| |
| /** |
| * Whether this task can deal with non-file resources. |
| * |
| * <p>This implementation returns true only if this task is |
| * <tar>. Any subclass of this class that also wants to |
| * support non-file resources needs to override this method. We |
| * need to do so for backwards compatibility reasons since we |
| * can't expect subclasses to support resources.</p> |
| * @return true for this task. |
| * @since Ant 1.7 |
| */ |
| protected boolean supportsNonFileResources() { |
| return getClass().equals(Tar.class); |
| } |
| |
| /** |
| * Checks whether the archive is out-of-date with respect to the resources |
| * of the given collection. |
| * |
| * <p>Also checks that either all collections only contain file |
| * resources or this class supports non-file collections.</p> |
| * |
| * <p>And - in case of file-collections - ensures that the archive won't |
| * contain itself.</p> |
| * |
| * @param rc the resource collection to check |
| * @return whether the archive is up-to-date |
| * @since Ant 1.7 |
| */ |
| protected boolean check(final ResourceCollection rc) { |
| boolean upToDate = true; |
| if (isFileFileSet(rc)) { |
| final FileSet fs = (FileSet) rc; |
| upToDate = check(fs.getDir(getProject()), getFileNames(fs)); |
| } else if (!rc.isFilesystemOnly() && !supportsNonFileResources()) { |
| throw new BuildException("only filesystem resources are supported"); |
| } else if (rc.isFilesystemOnly()) { |
| final Set<File> basedirs = new HashSet<>(); |
| final Map<File, List<String>> basedirToFilesMap = new HashMap<>(); |
| for (final Resource res : rc) { |
| final FileResource r = ResourceUtils |
| .asFileResource(res.as(FileProvider.class)); |
| File base = r.getBaseDir(); |
| if (base == null) { |
| base = Copy.NULL_FILE_PLACEHOLDER; |
| } |
| basedirs.add(base); |
| List<String> files = basedirToFilesMap.computeIfAbsent(base, k -> new Vector<>()); |
| if (base == Copy.NULL_FILE_PLACEHOLDER) { |
| files.add(r.getFile().getAbsolutePath()); |
| } else { |
| files.add(r.getName()); |
| } |
| } |
| for (final File base : basedirs) { |
| final File tmpBase = base == Copy.NULL_FILE_PLACEHOLDER ? null : base; |
| final List<String> files = basedirToFilesMap.get(base); |
| upToDate &= check(tmpBase, files); |
| } |
| } else { // non-file resources |
| for (Resource r : rc) { |
| upToDate = archiveIsUpToDate(r); |
| } |
| } |
| return upToDate; |
| } |
| |
| /** |
| * <p>Checks whether the archive is out-of-date with respect to the |
| * given files, ensures that the archive won't contain itself.</p> |
| * |
| * @param basedir base directory for file names |
| * @param files array of relative file names |
| * @return whether the archive is up-to-date |
| * @since Ant 1.7 |
| */ |
| protected boolean check(final File basedir, final String[] files) { |
| boolean upToDate = true; |
| if (!archiveIsUpToDate(files, basedir)) { |
| upToDate = false; |
| } |
| |
| for (String file : files) { |
| if (tarFile.equals(new File(basedir, file))) { |
| throw new BuildException("A tar file cannot include itself", |
| getLocation()); |
| } |
| } |
| return upToDate; |
| } |
| |
| /** |
| * <p>Checks whether the archive is out-of-date with respect to the |
| * given files, ensures that the archive won't contain itself.</p> |
| * |
| * @param basedir base directory for file names |
| * @param files array of relative file names |
| * @return whether the archive is up-to-date |
| * @see #check(File, String[]) |
| * @since Ant 1.9.5 |
| */ |
| protected boolean check(final File basedir, final Collection<String> files) { |
| return check(basedir, files.toArray(new String[files.size()])); |
| } |
| |
| /** |
| * Adds the resources contained in this collection to the archive. |
| * |
| * <p>Uses the file based methods for file resources for backwards |
| * compatibility.</p> |
| * |
| * @param rc the collection containing resources to add |
| * @param tOut stream writing to the archive. |
| * @throws IOException on error. |
| * @since Ant 1.7 |
| */ |
| protected void tar(final ResourceCollection rc, final TarOutputStream tOut) |
| throws IOException { |
| ArchiveFileSet afs = null; |
| if (rc instanceof ArchiveFileSet) { |
| afs = (ArchiveFileSet) rc; |
| } |
| if (afs != null && afs.size() > 1 |
| && !afs.getFullpath(this.getProject()).isEmpty()) { |
| throw new BuildException( |
| "fullpath attribute may only be specified for filesets that specify a single file."); |
| } |
| final TarFileSet tfs = asTarFileSet(afs); |
| |
| if (isFileFileSet(rc)) { |
| final FileSet fs = (FileSet) rc; |
| for (String file : getFileNames(fs)) { |
| final File f = new File(fs.getDir(getProject()), file); |
| final String name = file.replace(File.separatorChar, '/'); |
| tarFile(f, tOut, name, tfs); |
| } |
| } else if (rc.isFilesystemOnly()) { |
| for (final Resource r : rc) { |
| final File f = r.as(FileProvider.class).getFile(); |
| tarFile(f, tOut, f.getName(), tfs); |
| } |
| } else { // non-file resources |
| for (final Resource r : rc) { |
| tarResource(r, tOut, r.getName(), tfs); |
| } |
| } |
| } |
| |
| /** |
| * whether the given resource collection is a (subclass of) |
| * FileSet that only contains file system resources. |
| * @param rc the resource collection to check. |
| * @return true if the collection is a fileset. |
| * @since Ant 1.7 |
| */ |
| protected static boolean isFileFileSet(final ResourceCollection rc) { |
| return rc instanceof FileSet && rc.isFilesystemOnly(); |
| } |
| |
| /** |
| * Grabs all included files and directors from the FileSet and |
| * returns them as an array of (relative) file names. |
| * @param fs the fileset to operate on. |
| * @return a list of the filenames. |
| * @since Ant 1.7 |
| */ |
| protected static String[] getFileNames(final FileSet fs) { |
| final DirectoryScanner ds = fs.getDirectoryScanner(fs.getProject()); |
| final String[] directories = ds.getIncludedDirectories(); |
| final String[] filesPerSe = ds.getIncludedFiles(); |
| final String[] files = new String[directories.length + filesPerSe.length]; |
| System.arraycopy(directories, 0, files, 0, directories.length); |
| System.arraycopy(filesPerSe, 0, files, directories.length, |
| filesPerSe.length); |
| return files; |
| } |
| |
| /** |
| * Copies fullpath, prefix and permission attributes from the |
| * ArchiveFileSet to a new TarFileSet (or returns it unchanged if |
| * it already is a TarFileSet). |
| * |
| * @param archiveFileSet fileset to copy attributes from, may be null |
| * @return a new TarFileSet. |
| * @since Ant 1.7 |
| */ |
| protected TarFileSet asTarFileSet(final ArchiveFileSet archiveFileSet) { |
| TarFileSet tfs; |
| if (archiveFileSet instanceof TarFileSet) { |
| tfs = (TarFileSet) archiveFileSet; |
| } else { |
| tfs = new TarFileSet(); |
| tfs.setProject(getProject()); |
| if (archiveFileSet != null) { |
| tfs.setPrefix(archiveFileSet.getPrefix(getProject())); |
| tfs.setFullpath(archiveFileSet.getFullpath(getProject())); |
| if (archiveFileSet.hasFileModeBeenSet()) { |
| tfs.integerSetFileMode(archiveFileSet |
| .getFileMode(getProject())); |
| } |
| if (archiveFileSet.hasDirModeBeenSet()) { |
| tfs.integerSetDirMode(archiveFileSet |
| .getDirMode(getProject())); |
| } |
| |
| if (archiveFileSet |
| instanceof org.apache.tools.ant.types.TarFileSet) { |
| final org.apache.tools.ant.types.TarFileSet t = |
| (org.apache.tools.ant.types.TarFileSet) archiveFileSet; |
| if (t.hasUserNameBeenSet()) { |
| tfs.setUserName(t.getUserName()); |
| } |
| if (t.hasGroupBeenSet()) { |
| tfs.setGroup(t.getGroup()); |
| } |
| if (t.hasUserIdBeenSet()) { |
| tfs.setUid(t.getUid()); |
| } |
| if (t.hasGroupIdBeenSet()) { |
| tfs.setGid(t.getGid()); |
| } |
| } |
| } |
| } |
| return tfs; |
| } |
| |
| /** |
| * This is a FileSet with the option to specify permissions |
| * and other attributes. |
| */ |
| public static class TarFileSet |
| extends org.apache.tools.ant.types.TarFileSet { |
| private String[] files = null; |
| |
| private boolean preserveLeadingSlashes = false; |
| |
| /** |
| * Creates a new <code>TarFileSet</code> instance. |
| * Using a fileset as a constructor argument. |
| * |
| * @param fileset a <code>FileSet</code> value |
| */ |
| public TarFileSet(final FileSet fileset) { |
| super(fileset); |
| } |
| |
| /** |
| * Creates a new <code>TarFileSet</code> instance. |
| * |
| */ |
| public TarFileSet() { |
| super(); |
| } |
| |
| /** |
| * Get a list of files and directories specified in the fileset. |
| * @param p the current project. |
| * @return a list of file and directory names, relative to |
| * the baseDir for the project. |
| */ |
| public String[] getFiles(final Project p) { |
| if (files == null) { |
| files = getFileNames(this); |
| } |
| return files; |
| } |
| |
| /** |
| * A 3 digit octal string, specify the user, group and |
| * other modes in the standard Unix fashion; |
| * optional, default=0644 |
| * @param octalString a 3 digit octal string. |
| */ |
| public void setMode(final String octalString) { |
| setFileMode(octalString); |
| } |
| |
| /** |
| * @return the current mode. |
| */ |
| public int getMode() { |
| return getFileMode(this.getProject()); |
| } |
| |
| /** |
| * Flag to indicates whether leading `/'s should |
| * be preserved in the file names. |
| * Optional, default is <code>false</code>. |
| * @param b the leading slashes flag. |
| */ |
| public void setPreserveLeadingSlashes(final boolean b) { |
| this.preserveLeadingSlashes = b; |
| } |
| |
| /** |
| * @return the leading slashes flag. |
| */ |
| public boolean getPreserveLeadingSlashes() { |
| return preserveLeadingSlashes; |
| } |
| } |
| |
| /** |
| * Set of options for long file handling in the task. |
| * |
| */ |
| public static class TarLongFileMode extends EnumeratedAttribute { |
| |
| /** permissible values for longfile attribute */ |
| public static final String |
| WARN = "warn", |
| FAIL = "fail", |
| TRUNCATE = "truncate", |
| GNU = "gnu", |
| POSIX = "posix", |
| OMIT = "omit"; |
| |
| private static final String[] VALID_MODES = { |
| WARN, FAIL, TRUNCATE, GNU, POSIX, OMIT |
| }; |
| |
| /** Constructor, defaults to "warn" */ |
| public TarLongFileMode() { |
| super(); |
| setValue(WARN); |
| } |
| |
| /** |
| * @return the possible values for this enumerated type. |
| */ |
| @Override |
| public String[] getValues() { |
| return VALID_MODES; |
| } |
| |
| /** |
| * @return true if value is "truncate". |
| */ |
| public boolean isTruncateMode() { |
| return TRUNCATE.equalsIgnoreCase(getValue()); |
| } |
| |
| /** |
| * @return true if value is "warn". |
| */ |
| public boolean isWarnMode() { |
| return WARN.equalsIgnoreCase(getValue()); |
| } |
| |
| /** |
| * @return true if value is "gnu". |
| */ |
| public boolean isGnuMode() { |
| return GNU.equalsIgnoreCase(getValue()); |
| } |
| |
| /** |
| * @return true if value is "fail". |
| */ |
| public boolean isFailMode() { |
| return FAIL.equalsIgnoreCase(getValue()); |
| } |
| |
| /** |
| * @return true if value is "omit". |
| */ |
| public boolean isOmitMode() { |
| return OMIT.equalsIgnoreCase(getValue()); |
| } |
| |
| /** |
| * @return true if value is "posix". |
| */ |
| public boolean isPosixMode() { |
| return POSIX.equalsIgnoreCase(getValue()); |
| } |
| } |
| |
| /** |
| * Valid Modes for Compression attribute to Tar Task |
| * |
| */ |
| public static final class TarCompressionMethod extends EnumeratedAttribute { |
| |
| // permissible values for compression attribute |
| /** |
| * No compression |
| */ |
| private static final String NONE = "none"; |
| /** |
| * GZIP compression |
| */ |
| private static final String GZIP = "gzip"; |
| /** |
| * BZIP2 compression |
| */ |
| private static final String BZIP2 = "bzip2"; |
| /** |
| * XZ compression |
| * @since 1.10.1 |
| */ |
| private static final String XZ = "xz"; |
| |
| /** |
| * Default constructor |
| */ |
| public TarCompressionMethod() { |
| super(); |
| setValue(NONE); |
| } |
| |
| /** |
| * Get valid enumeration values. |
| * @return valid enumeration values |
| */ |
| @Override |
| public String[] getValues() { |
| return new String[] {NONE, GZIP, BZIP2, XZ}; |
| } |
| |
| /** |
| * This method wraps the output stream with the |
| * corresponding compression method |
| * |
| * @param ostream output stream |
| * @return output stream with on-the-fly compression |
| * @exception IOException thrown if file is not writable |
| */ |
| private OutputStream compress(final OutputStream ostream) |
| throws IOException { |
| final String v = getValue(); |
| if (GZIP.equals(v)) { |
| return new GZIPOutputStream(ostream); |
| } |
| if (XZ.equals(v)) { |
| return newXZOutputStream(ostream); |
| } |
| if (BZIP2.equals(v)) { |
| ostream.write('B'); |
| ostream.write('Z'); |
| return new CBZip2OutputStream(ostream); |
| } |
| return ostream; |
| } |
| |
| private static OutputStream newXZOutputStream(OutputStream ostream) |
| throws BuildException { |
| try { |
| Class<?> fClazz = Class.forName("org.tukaani.xz.FilterOptions"); |
| Class<?> oClazz = Class.forName("org.tukaani.xz.LZMA2Options"); |
| Class<? extends OutputStream> sClazz = |
| Class.forName("org.tukaani.xz.XZOutputStream") |
| .asSubclass(OutputStream.class); |
| Constructor<? extends OutputStream> c = |
| sClazz.getConstructor(OutputStream.class, fClazz); |
| return c.newInstance(ostream, oClazz.getDeclaredConstructor().newInstance()); |
| } catch (ClassNotFoundException ex) { |
| throw new BuildException("xz compression requires the XZ for Java library", |
| ex); |
| } catch (NoSuchMethodException |
| | InstantiationException |
| | IllegalAccessException |
| | InvocationTargetException |
| ex) { |
| throw new BuildException("failed to create XZOutputStream", ex); |
| } |
| } |
| } |
| } |