Mercurial > cgi-bin > hgweb.cgi > JpegWasher
diff lib/mod/src/de/masters_of_disaster/ant/tasks/ar/Ar.java @ 33:3d86f0391168
Work on improving the build system.
author | David Barts <davidb@stashtea.com> |
---|---|
date | Fri, 24 Apr 2020 19:45:57 -0700 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/mod/src/de/masters_of_disaster/ant/tasks/ar/Ar.java Fri Apr 24 19:45:57 2020 -0700 @@ -0,0 +1,468 @@ +package de.masters_of_disaster.ant.tasks.ar; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.zip.UnixStat; + +/** + * Creates an ar archive. + * + * @ant.task category="packaging" + */ +public class Ar extends MatchingTask { + File destFile; + File baseDir; + + private ArLongFileMode longFileMode = new ArLongFileMode(); + + Vector filesets = new Vector(); + + /** + * Indicates whether the user has been warned about long files already. + */ + private boolean longWarningGiven = false; + + /** + * Add a new fileset with the option to specify permissions + * @return the ar fileset to be used as the nested element. + */ + public ArFileSet createArFileSet() { + ArFileSet fileset = new ArFileSet(); + filesets.addElement(fileset); + return fileset; + } + + + /** + * Set the name/location of where to create the ar file. + * @param destFile The output of the tar + */ + public void setDestFile(File destFile) { + this.destFile = destFile; + } + + /** + * This is the base directory to look in for things to ar. + * @param baseDir the base directory. + */ + public void setBasedir(File baseDir) { + this.baseDir = baseDir; + } + + /** + * Set how to handle long files, those with a name>16 chars or containing spaces. + * Optional, default=warn. + * <p> + * Allowable values are + * <ul> + * <li> truncate - names are truncated to the maximum length, spaces are replaced by '_' + * <li> fail - names greater than the maximum cause a build exception + * <li> warn - names greater than the maximum cause a warning and TRUNCATE is used + * <li> bsd - BSD variant is used if any names are greater than the maximum. + * <li> gnu - GNU variant is used if any names are greater than the maximum. + * <li> omit - files with a name greater than the maximum are omitted from the archive + * </ul> + * @param mode the mode to handle long file names. + */ + public void setLongfile(ArLongFileMode mode) { + this.longFileMode = mode; + } + + /** + * do the business + * @throws BuildException on error + */ + public void execute() throws BuildException { + if (destFile == null) { + throw new BuildException("destFile attribute must be set!", + getLocation()); + } + + if (destFile.exists() && destFile.isDirectory()) { + throw new BuildException("destFile is a directory!", + getLocation()); + } + + if (destFile.exists() && !destFile.canWrite()) { + throw new BuildException("Can not write to the specified destFile!", + getLocation()); + } + + Vector savedFileSets = (Vector) filesets.clone(); + 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. + ArFileSet mainFileSet = new ArFileSet(fileset); + mainFileSet.setDir(baseDir); + filesets.addElement(mainFileSet); + } + + if (filesets.size() == 0) { + throw new BuildException("You must supply either a basedir " + + "attribute or some nested filesets.", + getLocation()); + } + + // check if ar is out of date with respect to each + // fileset + boolean upToDate = true; + for (Enumeration e = filesets.elements(); e.hasMoreElements();) { + ArFileSet fs = (ArFileSet) e.nextElement(); + String[] files = fs.getFiles(getProject()); + + if (!archiveIsUpToDate(files, fs.getDir(getProject()))) { + upToDate = false; + } + + for (int i = 0; i < files.length; ++i) { + if (destFile.equals(new File(fs.getDir(getProject()), + files[i]))) { + throw new BuildException("An ar file cannot include " + + "itself", getLocation()); + } + } + } + + if (upToDate) { + log("Nothing to do: " + destFile.getAbsolutePath() + + " is up to date.", Project.MSG_INFO); + return; + } + + log("Building ar: " + destFile.getAbsolutePath(), Project.MSG_INFO); + + ArOutputStream aOut = null; + try { + aOut = new ArOutputStream( + new BufferedOutputStream( + new FileOutputStream(destFile))); + if (longFileMode.isTruncateMode() + || longFileMode.isWarnMode()) { + aOut.setLongFileMode(ArOutputStream.LONGFILE_TRUNCATE); + } else if (longFileMode.isFailMode() + || longFileMode.isOmitMode()) { + aOut.setLongFileMode(ArOutputStream.LONGFILE_ERROR); + } else if (longFileMode.isBsdMode()) { + aOut.setLongFileMode(ArOutputStream.LONGFILE_BSD); + } else { + // GNU + aOut.setLongFileMode(ArOutputStream.LONGFILE_GNU); + } + + longWarningGiven = false; + for (Enumeration e = filesets.elements(); + e.hasMoreElements();) { + ArFileSet fs = (ArFileSet) e.nextElement(); + String[] files = fs.getFiles(getProject()); + if (files.length > 1 && fs.getFullpath().length() > 0) { + throw new BuildException("fullpath attribute may only " + + "be specified for " + + "filesets that specify a " + + "single file."); + } + for (int i = 0; i < files.length; i++) { + File f = new File(fs.getDir(getProject()), files[i]); + arFile(f, aOut, fs); + } + } + } catch (IOException ioe) { + String msg = "Problem creating AR: " + ioe.getMessage(); + throw new BuildException(msg, ioe, getLocation()); + } finally { + FileUtils.close(aOut); + } + } finally { + filesets = savedFileSets; + } + } + + /** + * ar a file + * @param file the file to ar + * @param aOut the output stream + * @param arFileSet the fileset that the file came from. + * @throws IOException on error + */ + protected void arFile(File file, ArOutputStream aOut, ArFileSet arFileSet) + throws IOException { + FileInputStream fIn = null; + + if (file.isDirectory()) { + return; + } + + String fileName = file.getName(); + + String fullpath = arFileSet.getFullpath(); + if (fullpath.length() > 0) { + fileName = fullpath.substring(fullpath.lastIndexOf('/')); + } + + // don't add "" to the archive + if (fileName.length() <= 0) { + return; + } + + try { + if ((fileName.length() >= ArConstants.NAMELEN) + || (-1 != fileName.indexOf(' '))) { + if (longFileMode.isOmitMode()) { + log("Omitting: " + fileName, Project.MSG_INFO); + return; + } else if (longFileMode.isWarnMode()) { + if (!longWarningGiven) { + log("Resulting ar file contains truncated or space converted filenames", + Project.MSG_WARN); + longWarningGiven = true; + } + log("Entry: \"" + fileName + "\" longer than " + + ArConstants.NAMELEN + " characters or containing spaces.", + Project.MSG_WARN); + } else if (longFileMode.isFailMode()) { + throw new BuildException("Entry: \"" + fileName + + "\" longer than " + ArConstants.NAMELEN + + "characters or containting spaces.", getLocation()); + } + } + + ArEntry ae = new ArEntry(fileName); + ae.setFileDate(file.lastModified()); + ae.setUserId(arFileSet.getUid()); + ae.setGroupId(arFileSet.getGid()); + ae.setMode(arFileSet.getMode()); + ae.setSize(file.length()); + + aOut.putNextEntry(ae); + + fIn = new FileInputStream(file); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do { + aOut.write(buffer, 0, count); + count = fIn.read(buffer, 0, buffer.length); + } while (count != -1); + + aOut.closeEntry(); + } finally { + if (fIn != null) { + fIn.close(); + } + } + } + + /** + * 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. + */ + protected boolean archiveIsUpToDate(String[] files, File dir) { + SourceFileScanner sfs = new SourceFileScanner(this); + MergingMapper mm = new MergingMapper(); + mm.setTo(destFile.getAbsolutePath()); + return sfs.restrict(files, dir, null, mm).length == 0; + } + + /** + * This is a FileSet with the option to specify permissions + * and other attributes. + */ + public static class ArFileSet extends FileSet { + private String[] files = null; + + private int fileMode = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM; + private int uid; + private int gid; + private String fullpath = ""; + + /** + * Creates a new <code>ArFileSet</code> instance. + * Using a fileset as a constructor argument. + * + * @param fileset a <code>FileSet</code> value + */ + public ArFileSet(FileSet fileset) { + super(fileset); + } + + /** + * Creates a new <code>ArFileSet</code> instance. + * + */ + public ArFileSet() { + 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(Project p) { + if (files == null) { + DirectoryScanner ds = getDirectoryScanner(p); + files = ds.getIncludedFiles(); + } + + 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(String octalString) { + this.fileMode = + UnixStat.FILE_FLAG | Integer.parseInt(octalString, 8); + } + + /** + * @return the current mode. + */ + public int getMode() { + return fileMode; + } + + /** + * The UID for the ar entry; optional, default="0" + * @param uid the id of the user for the ar entry. + */ + public void setUid(int uid) { + this.uid = uid; + } + + /** + * @return the UID for the ar entry + */ + public int getUid() { + return uid; + } + + /** + * The GID for the ar entry; optional, default="0" + * @param gid the group id. + */ + public void setGid(int gid) { + this.gid = gid; + } + + /** + * @return the group identifier. + */ + public int getGid() { + return gid; + } + + /** + * If the fullpath attribute is set, the file in the fileset + * is written with the last part of the path in the archive. + * If the fullpath ends in '/' the file is omitted from the archive. + * It is an error to have more than one file specified in such a fileset. + * @param fullpath the path to use for the file in a fileset. + */ + public void setFullpath(String fullpath) { + this.fullpath = fullpath; + } + + /** + * @return the path to use for a single file fileset. + */ + public String getFullpath() { + return fullpath; + } + } + + /** + * Set of options for long file handling in the task. + */ + public static class ArLongFileMode extends EnumeratedAttribute { + /** permissible values for longfile attribute */ + public static final String + WARN = "warn", + FAIL = "fail", + TRUNCATE = "truncate", + GNU = "gnu", + BSD = "bsd", + OMIT = "omit"; + + private final String[] validModes = {WARN, FAIL, TRUNCATE, GNU, BSD, OMIT}; + + /** Constructor, defaults to "warn" */ + public ArLongFileMode() { + super(); + setValue(WARN); + } + + /** + * @return the possible values for this enumerated type. + */ + public String[] getValues() { + return validModes; + } + + /** + * @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 "bsd". + */ + public boolean isBsdMode() { + return BSD.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()); + } + } +}