001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs.optional;
020:
021: import java.io.File;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.OutputStream;
025: import java.io.PrintWriter;
026: import java.util.Enumeration;
027: import java.util.Vector;
028: import org.apache.tools.ant.BuildException;
029: import org.apache.tools.ant.DirectoryScanner;
030: import org.apache.tools.ant.Project;
031: import org.apache.tools.ant.taskdefs.ExecTask;
032: import org.apache.tools.ant.taskdefs.Execute;
033: import org.apache.tools.ant.taskdefs.LogOutputStream;
034: import org.apache.tools.ant.taskdefs.MatchingTask;
035: import org.apache.tools.ant.taskdefs.StreamPumper;
036: import org.apache.tools.ant.taskdefs.condition.Os;
037: import org.apache.tools.ant.types.FileSet;
038: import org.apache.tools.ant.util.FileUtils;
039:
040: /**
041: * Create a CAB archive.
042: *
043: */
044:
045: public class Cab extends MatchingTask {
046:
047: private File cabFile;
048: private File baseDir;
049: private Vector filesets = new Vector();
050: private boolean doCompress = true;
051: private boolean doVerbose = false;
052: private String cmdOptions;
053:
054: // CheckStyle:VisibilityModifier OFF - bc
055: protected String archiveType = "cab";
056: // CheckStyle:VisibilityModifier ON
057:
058: private static final FileUtils FILE_UTILS = FileUtils
059: .getFileUtils();
060:
061: /**
062: * The name/location of where to create the .cab file.
063: * @param cabFile the location of the cab file.
064: */
065: public void setCabfile(File cabFile) {
066: this .cabFile = cabFile;
067: }
068:
069: /**
070: * Base directory to look in for files to CAB.
071: * @param baseDir base directory for files to cab.
072: */
073: public void setBasedir(File baseDir) {
074: this .baseDir = baseDir;
075: }
076:
077: /**
078: * If true, compress the files otherwise only store them.
079: * @param compress a <code>boolean</code> value.
080: */
081: public void setCompress(boolean compress) {
082: doCompress = compress;
083: }
084:
085: /**
086: * If true, display cabarc output.
087: * @param verbose a <code>boolean</code> value.
088: */
089: public void setVerbose(boolean verbose) {
090: doVerbose = verbose;
091: }
092:
093: /**
094: * Sets additional cabarc options that are not supported directly.
095: * @param options cabarc command line options.
096: */
097: public void setOptions(String options) {
098: cmdOptions = options;
099: }
100:
101: /**
102: * Adds a set of files to archive.
103: * @param set a set of files to archive.
104: */
105: public void addFileset(FileSet set) {
106: if (filesets.size() > 0) {
107: throw new BuildException("Only one nested fileset allowed");
108: }
109: filesets.addElement(set);
110: }
111:
112: /*
113: * I'm not fond of this pattern: "sub-method expected to throw
114: * task-cancelling exceptions". It feels too much like programming
115: * for side-effects to me...
116: */
117: /**
118: * Check if the attributes and nested elements are correct.
119: * @throws BuildException on error.
120: */
121: protected void checkConfiguration() throws BuildException {
122: if (baseDir == null && filesets.size() == 0) {
123: throw new BuildException("basedir attribute or one "
124: + "nested fileset is required!", getLocation());
125: }
126: if (baseDir != null && !baseDir.exists()) {
127: throw new BuildException("basedir does not exist!",
128: getLocation());
129: }
130: if (baseDir != null && filesets.size() > 0) {
131: throw new BuildException(
132: "Both basedir attribute and a nested fileset is not allowed");
133: }
134: if (cabFile == null) {
135: throw new BuildException("cabfile attribute must be set!",
136: getLocation());
137: }
138: }
139:
140: /**
141: * Create a new exec delegate. The delegate task is populated so that
142: * it appears in the logs to be the same task as this one.
143: * @return the delegate.
144: * @throws BuildException on error.
145: */
146: protected ExecTask createExec() throws BuildException {
147: ExecTask exec = new ExecTask(this );
148: return exec;
149: }
150:
151: /**
152: * Check to see if the target is up to date with respect to input files.
153: * @param files the list of files to check.
154: * @return true if the cab file is newer than its dependents.
155: */
156: protected boolean isUpToDate(Vector files) {
157: boolean upToDate = true;
158: for (int i = 0; i < files.size() && upToDate; i++) {
159: String file = files.elementAt(i).toString();
160: if (FILE_UTILS.resolveFile(baseDir, file).lastModified() > cabFile
161: .lastModified()) {
162: upToDate = false;
163: }
164: }
165: return upToDate;
166: }
167:
168: /**
169: * Creates a list file. This temporary file contains a list of all files
170: * to be included in the cab, one file per line.
171: *
172: * <p>This method expects to only be called on Windows and thus
173: * quotes the file names.</p>
174: * @param files the list of files to use.
175: * @return the list file created.
176: * @throws IOException if there is an error.
177: */
178: protected File createListFile(Vector files) throws IOException {
179: File listFile = FILE_UTILS.createTempFile("ant", "", null);
180: listFile.deleteOnExit();
181:
182: PrintWriter writer = new PrintWriter(new FileOutputStream(
183: listFile));
184:
185: int size = files.size();
186: for (int i = 0; i < size; i++) {
187: writer.println('\"' + files.elementAt(i).toString() + '\"');
188: }
189: writer.close();
190:
191: return listFile;
192: }
193:
194: /**
195: * Append all files found by a directory scanner to a vector.
196: * @param files the vector to append the files to.
197: * @param ds the scanner to get the files from.
198: */
199: protected void appendFiles(Vector files, DirectoryScanner ds) {
200: String[] dsfiles = ds.getIncludedFiles();
201:
202: for (int i = 0; i < dsfiles.length; i++) {
203: files.addElement(dsfiles[i]);
204: }
205: }
206:
207: /**
208: * Get the complete list of files to be included in the cab. Filenames
209: * are gathered from the fileset if it has been added, otherwise from the
210: * traditional include parameters.
211: * @return the list of files.
212: * @throws BuildException if there is an error.
213: */
214: protected Vector getFileList() throws BuildException {
215: Vector files = new Vector();
216:
217: if (baseDir != null) {
218: // get files from old methods - includes and nested include
219: appendFiles(files, super .getDirectoryScanner(baseDir));
220: } else {
221: FileSet fs = (FileSet) filesets.elementAt(0);
222: baseDir = fs.getDir();
223: appendFiles(files, fs.getDirectoryScanner(getProject()));
224: }
225:
226: return files;
227: }
228:
229: /**
230: * execute this task.
231: * @throws BuildException on error.
232: */
233: public void execute() throws BuildException {
234:
235: checkConfiguration();
236:
237: Vector files = getFileList();
238:
239: // quick exit if the target is up to date
240: if (isUpToDate(files)) {
241: return;
242: }
243:
244: log("Building " + archiveType + ": "
245: + cabFile.getAbsolutePath());
246:
247: if (!Os.isFamily("windows")) {
248: log("Using listcab/libcabinet", Project.MSG_VERBOSE);
249:
250: StringBuffer sb = new StringBuffer();
251:
252: Enumeration fileEnum = files.elements();
253:
254: while (fileEnum.hasMoreElements()) {
255: sb.append(fileEnum.nextElement()).append("\n");
256: }
257: sb.append("\n").append(cabFile.getAbsolutePath()).append(
258: "\n");
259:
260: try {
261: Process p = Execute.launch(getProject(),
262: new String[] { "listcab" }, null,
263: baseDir != null ? baseDir : getProject()
264: .getBaseDir(), true);
265: OutputStream out = p.getOutputStream();
266:
267: // Create the stream pumpers to forward listcab's stdout and stderr to the log
268: // note: listcab is an interactive program, and issues prompts for every new line.
269: // Therefore, make it show only with verbose logging turned on.
270: LogOutputStream outLog = new LogOutputStream(this ,
271: Project.MSG_VERBOSE);
272: LogOutputStream errLog = new LogOutputStream(this ,
273: Project.MSG_ERR);
274: StreamPumper outPump = new StreamPumper(p
275: .getInputStream(), outLog);
276: StreamPumper errPump = new StreamPumper(p
277: .getErrorStream(), errLog);
278:
279: // Pump streams asynchronously
280: (new Thread(outPump)).start();
281: (new Thread(errPump)).start();
282:
283: out.write(sb.toString().getBytes());
284: out.flush();
285: out.close();
286:
287: int result = -99; // A wild default for when the thread is interrupted
288:
289: try {
290: // Wait for the process to finish
291: result = p.waitFor();
292:
293: // Wait for the end of output and error streams
294: outPump.waitFor();
295: outLog.close();
296: errPump.waitFor();
297: errLog.close();
298: } catch (InterruptedException ie) {
299: log("Thread interrupted: " + ie);
300: }
301:
302: // Informative summary message in case of errors
303: if (Execute.isFailure(result)) {
304: log("Error executing listcab; error code: "
305: + result);
306: }
307: } catch (IOException ex) {
308: String msg = "Problem creating " + cabFile + " "
309: + ex.getMessage();
310: throw new BuildException(msg, getLocation());
311: }
312: } else {
313: try {
314: File listFile = createListFile(files);
315: ExecTask exec = createExec();
316: File outFile = null;
317:
318: // die if cabarc fails
319: exec.setFailonerror(true);
320: exec.setDir(baseDir);
321:
322: if (!doVerbose) {
323: outFile = FILE_UTILS
324: .createTempFile("ant", "", null);
325: outFile.deleteOnExit();
326: exec.setOutput(outFile);
327: }
328:
329: exec.setExecutable("cabarc");
330: exec.createArg().setValue("-r");
331: exec.createArg().setValue("-p");
332:
333: if (!doCompress) {
334: exec.createArg().setValue("-m");
335: exec.createArg().setValue("none");
336: }
337:
338: if (cmdOptions != null) {
339: exec.createArg().setLine(cmdOptions);
340: }
341:
342: exec.createArg().setValue("n");
343: exec.createArg().setFile(cabFile);
344: exec.createArg().setValue(
345: "@" + listFile.getAbsolutePath());
346:
347: exec.execute();
348:
349: if (outFile != null) {
350: outFile.delete();
351: }
352:
353: listFile.delete();
354: } catch (IOException ioe) {
355: String msg = "Problem creating " + cabFile + " "
356: + ioe.getMessage();
357: throw new BuildException(msg, getLocation());
358: }
359: }
360: }
361: }
|