1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional;
  18. import java.io.File;
  19. import java.io.FileOutputStream;
  20. import java.io.IOException;
  21. import java.io.OutputStream;
  22. import java.io.PrintWriter;
  23. import java.util.Enumeration;
  24. import java.util.Vector;
  25. import org.apache.tools.ant.BuildException;
  26. import org.apache.tools.ant.DirectoryScanner;
  27. import org.apache.tools.ant.Project;
  28. import org.apache.tools.ant.taskdefs.ExecTask;
  29. import org.apache.tools.ant.taskdefs.Execute;
  30. import org.apache.tools.ant.taskdefs.LogOutputStream;
  31. import org.apache.tools.ant.taskdefs.MatchingTask;
  32. import org.apache.tools.ant.taskdefs.StreamPumper;
  33. import org.apache.tools.ant.taskdefs.condition.Os;
  34. import org.apache.tools.ant.types.FileSet;
  35. import org.apache.tools.ant.util.FileUtils;
  36. /**
  37. * Create a CAB archive.
  38. *
  39. */
  40. public class Cab extends MatchingTask {
  41. private File cabFile;
  42. private File baseDir;
  43. private Vector filesets = new Vector();
  44. private boolean doCompress = true;
  45. private boolean doVerbose = false;
  46. private String cmdOptions;
  47. protected String archiveType = "cab";
  48. private FileUtils fileUtils = FileUtils.newFileUtils();
  49. /**
  50. * The name/location of where to create the .cab file.
  51. */
  52. public void setCabfile(File cabFile) {
  53. this.cabFile = cabFile;
  54. }
  55. /**
  56. * Base directory to look in for files to CAB.
  57. */
  58. public void setBasedir(File baseDir) {
  59. this.baseDir = baseDir;
  60. }
  61. /**
  62. * If true, compress the files otherwise only store them.
  63. */
  64. public void setCompress(boolean compress) {
  65. doCompress = compress;
  66. }
  67. /**
  68. * If true, display cabarc output.
  69. */
  70. public void setVerbose(boolean verbose) {
  71. doVerbose = verbose;
  72. }
  73. /**
  74. * Sets additional cabarc options that are not supported directly.
  75. */
  76. public void setOptions(String options) {
  77. cmdOptions = options;
  78. }
  79. /**
  80. * Adds a set of files to archive.
  81. */
  82. public void addFileset(FileSet set) {
  83. filesets.addElement(set);
  84. }
  85. /*
  86. * I'm not fond of this pattern: "sub-method expected to throw
  87. * task-cancelling exceptions". It feels too much like programming
  88. * for side-effects to me...
  89. */
  90. protected void checkConfiguration() throws BuildException {
  91. if (baseDir == null && filesets.size() == 0) {
  92. throw new BuildException("basedir attribute or at least one "
  93. + "nested filest is required!",
  94. getLocation());
  95. }
  96. if (baseDir != null && !baseDir.exists()) {
  97. throw new BuildException("basedir does not exist!", getLocation());
  98. }
  99. if (cabFile == null) {
  100. throw new BuildException("cabfile attribute must be set!",
  101. getLocation());
  102. }
  103. }
  104. /**
  105. * Create a new exec delegate. The delegate task is populated so that
  106. * it appears in the logs to be the same task as this one.
  107. */
  108. protected ExecTask createExec() throws BuildException {
  109. ExecTask exec = (ExecTask) getProject().createTask("exec");
  110. exec.setOwningTarget(this.getOwningTarget());
  111. exec.setTaskName(this.getTaskName());
  112. exec.setDescription(this.getDescription());
  113. return exec;
  114. }
  115. /**
  116. * Check to see if the target is up to date with respect to input files.
  117. * @return true if the cab file is newer than its dependents.
  118. */
  119. protected boolean isUpToDate(Vector files) {
  120. boolean upToDate = true;
  121. for (int i = 0; i < files.size() && upToDate; i++) {
  122. String file = files.elementAt(i).toString();
  123. if (fileUtils.resolveFile(baseDir, file).lastModified()
  124. > cabFile.lastModified()) {
  125. upToDate = false;
  126. }
  127. }
  128. return upToDate;
  129. }
  130. /**
  131. * Creates a list file. This temporary file contains a list of all files
  132. * to be included in the cab, one file per line.
  133. */
  134. protected File createListFile(Vector files)
  135. throws IOException {
  136. File listFile = fileUtils.createTempFile("ant", "", null);
  137. listFile.deleteOnExit();
  138. PrintWriter writer = new PrintWriter(new FileOutputStream(listFile));
  139. for (int i = 0; i < files.size(); i++) {
  140. writer.println(files.elementAt(i).toString());
  141. }
  142. writer.close();
  143. return listFile;
  144. }
  145. /**
  146. * Append all files found by a directory scanner to a vector.
  147. */
  148. protected void appendFiles(Vector files, DirectoryScanner ds) {
  149. String[] dsfiles = ds.getIncludedFiles();
  150. for (int i = 0; i < dsfiles.length; i++) {
  151. files.addElement(dsfiles[i]);
  152. }
  153. }
  154. /**
  155. * Get the complete list of files to be included in the cab. Filenames
  156. * are gathered from filesets if any have been added, otherwise from the
  157. * traditional include parameters.
  158. */
  159. protected Vector getFileList() throws BuildException {
  160. Vector files = new Vector();
  161. if (baseDir != null) {
  162. // get files from old methods - includes and nested include
  163. appendFiles(files, super.getDirectoryScanner(baseDir));
  164. }
  165. // get files from filesets
  166. for (int i = 0; i < filesets.size(); i++) {
  167. FileSet fs = (FileSet) filesets.elementAt(i);
  168. if (fs != null) {
  169. appendFiles(files, fs.getDirectoryScanner(getProject()));
  170. }
  171. }
  172. return files;
  173. }
  174. public void execute() throws BuildException {
  175. checkConfiguration();
  176. Vector files = getFileList();
  177. // quick exit if the target is up to date
  178. if (isUpToDate(files)) {
  179. return;
  180. }
  181. log("Building " + archiveType + ": " + cabFile.getAbsolutePath());
  182. if (!Os.isFamily("windows")) {
  183. log("Using listcab/libcabinet", Project.MSG_VERBOSE);
  184. StringBuffer sb = new StringBuffer();
  185. Enumeration fileEnum = files.elements();
  186. while (fileEnum.hasMoreElements()) {
  187. sb.append(fileEnum.nextElement()).append("\n");
  188. }
  189. sb.append("\n").append(cabFile.getAbsolutePath()).append("\n");
  190. try {
  191. Process p = Execute.launch(getProject(),
  192. new String[] {"listcab"}, null,
  193. baseDir != null ? baseDir
  194. : getProject().getBaseDir(),
  195. true);
  196. OutputStream out = p.getOutputStream();
  197. // Create the stream pumpers to forward listcab's stdout and stderr to the log
  198. // note: listcab is an interactive program, and issues prompts for every new line.
  199. // Therefore, make it show only with verbose logging turned on.
  200. LogOutputStream outLog = new LogOutputStream(this, Project.MSG_VERBOSE);
  201. LogOutputStream errLog = new LogOutputStream(this, Project.MSG_ERR);
  202. StreamPumper outPump = new StreamPumper(p.getInputStream(), outLog);
  203. StreamPumper errPump = new StreamPumper(p.getErrorStream(), errLog);
  204. // Pump streams asynchronously
  205. (new Thread(outPump)).start();
  206. (new Thread(errPump)).start();
  207. out.write(sb.toString().getBytes());
  208. out.flush();
  209. out.close();
  210. int result = -99; // A wild default for when the thread is interrupted
  211. try {
  212. // Wait for the process to finish
  213. result = p.waitFor();
  214. // Wait for the end of output and error streams
  215. outPump.waitFor();
  216. outLog.close();
  217. errPump.waitFor();
  218. errLog.close();
  219. } catch (InterruptedException ie) {
  220. log("Thread interrupted: " + ie);
  221. }
  222. // Informative summary message in case of errors
  223. if (Execute.isFailure(result)) {
  224. log("Error executing listcab; error code: " + result);
  225. }
  226. } catch (IOException ex) {
  227. String msg = "Problem creating " + cabFile + " " + ex.getMessage();
  228. throw new BuildException(msg, getLocation());
  229. }
  230. } else {
  231. try {
  232. File listFile = createListFile(files);
  233. ExecTask exec = createExec();
  234. File outFile = null;
  235. // die if cabarc fails
  236. exec.setFailonerror(true);
  237. exec.setDir(baseDir);
  238. if (!doVerbose) {
  239. outFile = fileUtils.createTempFile("ant", "", null);
  240. outFile.deleteOnExit();
  241. exec.setOutput(outFile);
  242. }
  243. exec.setExecutable("cabarc");
  244. exec.createArg().setValue("-r");
  245. exec.createArg().setValue("-p");
  246. if (!doCompress) {
  247. exec.createArg().setValue("-m");
  248. exec.createArg().setValue("none");
  249. }
  250. if (cmdOptions != null) {
  251. exec.createArg().setLine(cmdOptions);
  252. }
  253. exec.createArg().setValue("n");
  254. exec.createArg().setFile(cabFile);
  255. exec.createArg().setValue("@" + listFile.getAbsolutePath());
  256. exec.execute();
  257. if (outFile != null) {
  258. outFile.delete();
  259. }
  260. listFile.delete();
  261. } catch (IOException ioe) {
  262. String msg = "Problem creating " + cabFile + " " + ioe.getMessage();
  263. throw new BuildException(msg, getLocation());
  264. }
  265. }
  266. }
  267. }