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. /*
  18. * build notes
  19. * The reference CD to listen to while editing this file is
  20. * Underworld Everything, Everything
  21. * variable naming policy from Fowler's refactoring book.
  22. */
  23. // place below the optional ant tasks package
  24. package org.apache.tools.ant.taskdefs.optional.dotnet;
  25. // imports
  26. import java.io.File;
  27. import java.io.IOException;
  28. import java.io.FileOutputStream;
  29. import java.io.PrintWriter;
  30. import java.io.BufferedOutputStream;
  31. import java.io.FileNotFoundException;
  32. import java.util.Hashtable;
  33. import org.apache.tools.ant.BuildException;
  34. import org.apache.tools.ant.Project;
  35. import org.apache.tools.ant.Task;
  36. import org.apache.tools.ant.DirectoryScanner;
  37. import org.apache.tools.ant.util.FileUtils;
  38. import org.apache.tools.ant.taskdefs.Execute;
  39. import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
  40. import org.apache.tools.ant.taskdefs.LogStreamHandler;
  41. import org.apache.tools.ant.types.Commandline;
  42. /**
  43. * This is a helper class to spawn net commands out. In its initial form it
  44. * contains no .net specifics, just contains all the command line/exe
  45. * construction stuff. However, it may be handy in future to have a means of
  46. * setting the path to point to the dotnet bin directory; in which case the
  47. * shared code should go in here.
  48. *
  49. *@version 0.5
  50. */
  51. public class NetCommand {
  52. /**
  53. * owner project
  54. */
  55. protected Task owner;
  56. /**
  57. * executable
  58. */
  59. protected Execute executable;
  60. /**
  61. * what is the command line
  62. */
  63. protected Commandline commandLine;
  64. /**
  65. * title of the command
  66. */
  67. protected String title;
  68. /**
  69. * actual program to invoke
  70. */
  71. protected String program;
  72. /**
  73. * trace flag
  74. */
  75. protected boolean traceCommandLine = false;
  76. /**
  77. * flag to control action on execution trouble
  78. */
  79. protected boolean failOnError;
  80. /**
  81. * the directory to execute the command in. When null, the current
  82. * directory is used.
  83. */
  84. private File directory;
  85. /**
  86. * flag to set to to use @file based command cache
  87. */
  88. private boolean useResponseFile=false;
  89. /**
  90. * name of a temp file; may be null
  91. */
  92. private File temporaryCommandFile;
  93. /**
  94. * internal threshold for auto-switch
  95. */
  96. private int automaticResponseFileThreshold = 64;
  97. /**
  98. * constructor
  99. *
  100. *@param title (for logging/errors)
  101. *@param owner owner task
  102. *@param program app we are to run
  103. */
  104. public NetCommand(Task owner, String title, String program) {
  105. this.owner = owner;
  106. this.title = title;
  107. this.program = program;
  108. commandLine = new Commandline();
  109. commandLine.setExecutable(program);
  110. prepareExecutor();
  111. }
  112. /**
  113. * turn tracing on or off
  114. *
  115. *@param b trace flag
  116. */
  117. public void setTraceCommandLine(boolean b) {
  118. traceCommandLine = b;
  119. }
  120. /**
  121. * set fail on error flag
  122. *
  123. *@param b fail flag -set to true to cause an exception to be raised if
  124. * the return value != 0
  125. */
  126. public void setFailOnError(boolean b) {
  127. failOnError = b;
  128. }
  129. /**
  130. * query fail on error flag
  131. *
  132. *@return The failFailOnError value
  133. */
  134. public boolean getFailFailOnError() {
  135. return failOnError;
  136. }
  137. /**
  138. * set the directory to run from, if the default is inadequate
  139. * @param directory
  140. */
  141. public void setDirectory(File directory) {
  142. this.directory = directory;
  143. }
  144. /**
  145. * verbose text log
  146. *
  147. *@param msg string to add to log if verbose is defined for the build
  148. */
  149. protected void logVerbose(String msg) {
  150. owner.getProject().log(msg, Project.MSG_VERBOSE);
  151. }
  152. /**
  153. * error text log
  154. *
  155. *@param msg message to display as an error
  156. */
  157. protected void logError(String msg) {
  158. owner.getProject().log(msg, Project.MSG_ERR);
  159. }
  160. /**
  161. * add an argument to a command line; do nothing if the arg is null or
  162. * empty string
  163. *
  164. *@param argument The feature to be added to the Argument attribute
  165. */
  166. public void addArgument(String argument) {
  167. if (argument != null && argument.length() != 0) {
  168. commandLine.createArgument().setValue(argument);
  169. }
  170. }
  171. /**
  172. * concatenate two strings together and add them as a single argument,
  173. * but only if argument2 is non-null and non-zero length
  174. *
  175. *@param argument1 The first argument
  176. *@param argument2 The second argument
  177. */ public void addArgument(String argument1, String argument2) {
  178. if (argument2 != null && argument2.length() != 0) {
  179. commandLine.createArgument().setValue(argument1 + argument2);
  180. }
  181. }
  182. /**
  183. * getter
  184. * @return response file state
  185. */
  186. public boolean isUseResponseFile() {
  187. return useResponseFile;
  188. }
  189. /**
  190. * set this to true to always use the response file
  191. * @param useResponseFile
  192. */
  193. public void setUseResponseFile(boolean useResponseFile) {
  194. this.useResponseFile = useResponseFile;
  195. }
  196. /**
  197. * getter for threshold
  198. * @return 0 for disabled, or a threshold for enabling response files
  199. */
  200. public int getAutomaticResponseFileThreshold() {
  201. return automaticResponseFileThreshold;
  202. }
  203. /**
  204. * set threshold for automatically using response files -use 0 for off
  205. * @param automaticResponseFileThreshold
  206. */
  207. public void setAutomaticResponseFileThreshold(int automaticResponseFileThreshold) {
  208. this.automaticResponseFileThreshold = automaticResponseFileThreshold;
  209. }
  210. /**
  211. * set up the command sequence..
  212. */
  213. protected void prepareExecutor() {
  214. // default directory to the project's base directory
  215. if (owner == null) {
  216. throw new RuntimeException("no owner");
  217. }
  218. if (owner.getProject() == null) {
  219. throw new RuntimeException("Owner has no project");
  220. }
  221. File dir = owner.getProject().getBaseDir();
  222. if (directory != null) {
  223. dir = directory;
  224. }
  225. ExecuteStreamHandler handler = new LogStreamHandler(owner,
  226. Project.MSG_INFO, Project.MSG_WARN);
  227. executable = new Execute(handler, null);
  228. executable.setAntRun(owner.getProject());
  229. executable.setWorkingDirectory(dir);
  230. }
  231. /**
  232. * Run the command using the given Execute instance.
  233. *
  234. *@exception BuildException if something goes wrong and the
  235. * failOnError flag is true
  236. */
  237. public void runCommand()
  238. throws BuildException {
  239. int err = -1;
  240. // assume the worst
  241. try {
  242. if (traceCommandLine) {
  243. owner.log(commandLine.describeCommand());
  244. } else {
  245. //in verbose mode we always log stuff
  246. logVerbose(commandLine.describeCommand());
  247. }
  248. setExecutableCommandLine();
  249. err = executable.execute();
  250. if (Execute.isFailure(err)) {
  251. if (failOnError) {
  252. throw new BuildException(title + " returned: " + err, owner.getLocation());
  253. } else {
  254. owner.log(title + " Result: " + err, Project.MSG_ERR);
  255. }
  256. }
  257. } catch (IOException e) {
  258. throw new BuildException(title + " failed: " + e, e, owner.getLocation());
  259. } finally {
  260. if (temporaryCommandFile != null) {
  261. temporaryCommandFile.delete();
  262. }
  263. }
  264. }
  265. /**
  266. * set the executable command line
  267. */
  268. private void setExecutableCommandLine() {
  269. String[] commands = commandLine.getCommandline();
  270. //always trigger file mode if commands are big enough
  271. if (automaticResponseFileThreshold>0 &&
  272. commands.length > automaticResponseFileThreshold) {
  273. useResponseFile = true;
  274. }
  275. if (!useResponseFile || commands.length <= 1) {
  276. //the simple action is to send the command line in as is
  277. executable.setCommandline(commands);
  278. } else {
  279. //but for big operations, we save all the params to a temp file
  280. //and set @tmpfile as the command -then we remember to delete the tempfile
  281. //afterwards
  282. FileOutputStream fos = null;
  283. FileUtils fileUtils = FileUtils.newFileUtils();
  284. temporaryCommandFile = fileUtils.createTempFile("cmd", ".txt", null);
  285. owner.log("Using response file"+temporaryCommandFile,Project.MSG_VERBOSE);
  286. try {
  287. fos = new FileOutputStream(temporaryCommandFile);
  288. PrintWriter out = new PrintWriter(new BufferedOutputStream(fos));
  289. //start at 1 because element 0 is the executable name
  290. for (int i = 1; i < commands.length; ++i) {
  291. out.println(commands[i]);
  292. }
  293. out.flush();
  294. out.close();
  295. } catch (IOException ex) {
  296. throw new BuildException("saving command stream to " + temporaryCommandFile, ex);
  297. }
  298. String newCommandLine[] = new String[2];
  299. newCommandLine[0] = commands[0];
  300. newCommandLine[1] = "@" + temporaryCommandFile.getAbsolutePath();
  301. executable.setCommandline(newCommandLine);
  302. }
  303. }
  304. /**
  305. * scan through one fileset for files to include
  306. * @param scanner
  307. * @param filesToBuild
  308. * @param outputTimestamp timestamp to compare against
  309. * @return #of files out of date
  310. * @todo: should FAT granularity be included here?
  311. */
  312. public int scanOneFileset(DirectoryScanner scanner, Hashtable filesToBuild,
  313. long outputTimestamp) {
  314. int filesOutOfDate = 0;
  315. String[] dependencies = scanner.getIncludedFiles();
  316. File base = scanner.getBasedir();
  317. //add to the list
  318. for (int i = 0; i < dependencies.length; i++) {
  319. File targetFile = new File(base, dependencies[i]);
  320. if (filesToBuild.get(targetFile) == null) {
  321. filesToBuild.put(targetFile, targetFile);
  322. if (targetFile.lastModified() > outputTimestamp) {
  323. filesOutOfDate++;
  324. owner.log(targetFile.toString() + " is out of date",
  325. Project.MSG_VERBOSE);
  326. } else {
  327. owner.log(targetFile.toString(),
  328. Project.MSG_VERBOSE);
  329. }
  330. }
  331. }
  332. return filesOutOfDate;
  333. }
  334. }