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: * build notes
020: * The reference CD to listen to while editing this file is
021: * Underworld Everything, Everything
022: * variable naming policy from Fowler's refactoring book.
023: */
024: // place below the optional ant tasks package
025: package org.apache.tools.ant.taskdefs.optional.dotnet;
026:
027: // imports
028:
029: import java.io.File;
030: import java.io.IOException;
031: import java.io.FileOutputStream;
032: import java.io.PrintWriter;
033: import java.io.BufferedOutputStream;
034: import java.util.Hashtable;
035:
036: import org.apache.tools.ant.BuildException;
037: import org.apache.tools.ant.Project;
038: import org.apache.tools.ant.Task;
039: import org.apache.tools.ant.DirectoryScanner;
040: import org.apache.tools.ant.util.FileUtils;
041: import org.apache.tools.ant.taskdefs.Execute;
042: import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
043: import org.apache.tools.ant.taskdefs.LogStreamHandler;
044: import org.apache.tools.ant.types.Commandline;
045:
046: /**
047: * This is a helper class to spawn net commands out. In its initial form it
048: * contains no .net specifics, just contains all the command line/exe
049: * construction stuff. However, it may be handy in future to have a means of
050: * setting the path to point to the dotnet bin directory; in which case the
051: * shared code should go in here.
052: *
053: *@version 0.5
054: */
055:
056: public class NetCommand {
057:
058: private static final FileUtils FILE_UTILS = FileUtils
059: .getFileUtils();
060: // CheckStyle:VisibilityModifier OFF - bc
061:
062: /**
063: * owner project
064: */
065: protected Task owner;
066:
067: /**
068: * executable
069: */
070: protected Execute executable;
071:
072: /**
073: * what is the command line
074: */
075: protected Commandline commandLine;
076:
077: /**
078: * title of the command
079: */
080: protected String title;
081:
082: /**
083: * actual program to invoke
084: */
085: protected String program;
086:
087: /**
088: * trace flag
089: */
090: protected boolean traceCommandLine = false;
091:
092: /**
093: * flag to control action on execution trouble
094: */
095: protected boolean failOnError;
096:
097: // CheckStyle:VisibilityModifier ON
098:
099: /**
100: * the directory to execute the command in. When null, the current
101: * directory is used.
102: */
103: private File directory;
104:
105: /**
106: * flag to set to to use @file based command cache
107: */
108: private boolean useResponseFile = false;
109:
110: /**
111: * name of a temp file; may be null
112: */
113: private File temporaryCommandFile;
114:
115: /**
116: * internal threshold for auto-switch
117: */
118: private int automaticResponseFileThreshold = 64;
119:
120: /**
121: * constructor
122: *
123: *@param title (for logging/errors)
124: *@param owner owner task
125: *@param program app we are to run
126: */
127:
128: public NetCommand(Task owner, String title, String program) {
129: this .owner = owner;
130: this .title = title;
131: this .program = program;
132: commandLine = new Commandline();
133: commandLine.setExecutable(program);
134: }
135:
136: /**
137: * turn tracing on or off
138: *
139: *@param b trace flag
140: */
141: public void setTraceCommandLine(boolean b) {
142: traceCommandLine = b;
143: }
144:
145: /**
146: * set fail on error flag
147: *
148: *@param b fail flag -set to true to cause an exception to be raised if
149: * the return value != 0
150: */
151: public void setFailOnError(boolean b) {
152: failOnError = b;
153: }
154:
155: /**
156: * query fail on error flag
157: *
158: *@return The failFailOnError value
159: */
160: public boolean getFailFailOnError() {
161: return failOnError;
162: }
163:
164: /**
165: * set the directory to run from, if the default is inadequate
166: * @param directory the directory to use.
167: */
168: public void setDirectory(File directory) {
169: this .directory = directory;
170: }
171:
172: /**
173: * verbose text log
174: *
175: *@param msg string to add to log if verbose is defined for the build
176: */
177: protected void logVerbose(String msg) {
178: owner.getProject().log(msg, Project.MSG_VERBOSE);
179: }
180:
181: /**
182: * error text log
183: *
184: *@param msg message to display as an error
185: */
186: protected void logError(String msg) {
187: owner.getProject().log(msg, Project.MSG_ERR);
188: }
189:
190: /**
191: * add an argument to a command line; do nothing if the arg is null or
192: * empty string
193: *
194: *@param argument The feature to be added to the Argument attribute
195: */
196: public void addArgument(String argument) {
197: if (argument != null && argument.length() != 0) {
198: commandLine.createArgument().setValue(argument);
199: }
200: }
201:
202: /**
203: * add an argument to a command line; do nothing if the arg is null or
204: * empty string
205: *
206: *@param arguments The features to be added to the Argument attribute
207: */
208: public void addArguments(String[] arguments) {
209: if (arguments != null && arguments.length != 0) {
210: for (int i = 0; i < arguments.length; i++) {
211: addArgument(arguments[i]);
212: }
213: }
214: }
215:
216: /**
217: * concatenate two strings together and add them as a single argument,
218: * but only if argument2 is non-null and non-zero length
219: *
220: *@param argument1 The first argument
221: *@param argument2 The second argument
222: */
223: public void addArgument(String argument1, String argument2) {
224: if (argument2 != null && argument2.length() != 0) {
225: commandLine.createArgument()
226: .setValue(argument1 + argument2);
227: }
228: }
229:
230: /**
231: * getter
232: * @return response file state
233: */
234: public boolean isUseResponseFile() {
235: return useResponseFile;
236: }
237:
238: /**
239: * set this to true to always use the response file
240: * @param useResponseFile a <code>boolean</code> value.
241: */
242: public void setUseResponseFile(boolean useResponseFile) {
243: this .useResponseFile = useResponseFile;
244: }
245:
246: /**
247: * getter for threshold
248: * @return 0 for disabled, or a threshold for enabling response files
249: */
250: public int getAutomaticResponseFileThreshold() {
251: return automaticResponseFileThreshold;
252: }
253:
254: /**
255: * set threshold for automatically using response files -use 0 for off
256: * @param automaticResponseFileThreshold the threshold value to use.
257: */
258: public void setAutomaticResponseFileThreshold(
259: int automaticResponseFileThreshold) {
260: this .automaticResponseFileThreshold = automaticResponseFileThreshold;
261: }
262:
263: /**
264: * set up the command sequence..
265: */
266: protected void prepareExecutor() {
267: // default directory to the project's base directory
268: if (owner == null) {
269: throw new RuntimeException("no owner");
270: }
271: if (owner.getProject() == null) {
272: throw new RuntimeException("Owner has no project");
273: }
274: File dir = owner.getProject().getBaseDir();
275: if (directory != null) {
276: dir = directory;
277: }
278:
279: ExecuteStreamHandler handler = new LogStreamHandler(owner,
280: Project.MSG_INFO, Project.MSG_WARN);
281: executable = new Execute(handler, null);
282: executable.setAntRun(owner.getProject());
283: executable.setWorkingDirectory(dir);
284: }
285:
286: /**
287: * Run the command using the given Execute instance.
288: *
289: *@exception BuildException if something goes wrong and the
290: * failOnError flag is true
291: */
292: public void runCommand() throws BuildException {
293: prepareExecutor();
294: int err = -1;
295: // assume the worst
296: try {
297: if (traceCommandLine) {
298: owner.log("In directory "
299: + executable.getWorkingDirectory());
300: owner.log(commandLine.describeCommand());
301: } else {
302: //in verbose mode we always log stuff
303: logVerbose("In directory "
304: + executable.getWorkingDirectory());
305: logVerbose(commandLine.describeCommand());
306: }
307: setExecutableCommandLine();
308: err = executable.execute();
309: if (Execute.isFailure(err)) {
310: if (failOnError) {
311: throw new BuildException(title + " returned: "
312: + err, owner.getLocation());
313: } else {
314: owner.log(title + " Result: " + err,
315: Project.MSG_ERR);
316: }
317: }
318: } catch (IOException e) {
319: throw new BuildException(title + " failed: " + e, e, owner
320: .getLocation());
321: } finally {
322: if (temporaryCommandFile != null) {
323: temporaryCommandFile.delete();
324: }
325: }
326: }
327:
328: /**
329: * set the executable command line
330: */
331: private void setExecutableCommandLine() {
332:
333: String[] commands = commandLine.getCommandline();
334: //always trigger file mode if commands are big enough
335: if (automaticResponseFileThreshold > 0
336: && commands.length > automaticResponseFileThreshold) {
337: useResponseFile = true;
338: }
339: if (!useResponseFile || commands.length <= 1) {
340: //the simple action is to send the command line in as is
341: executable.setCommandline(commands);
342: } else {
343: //but for big operations, we save all the params to a temp file
344: //and set @tmpfile as the command -then we remember to delete the tempfile
345: //afterwards
346: FileOutputStream fos = null;
347:
348: temporaryCommandFile = FILE_UTILS.createTempFile("cmd",
349: ".txt", null);
350: owner.log("Using response file " + temporaryCommandFile,
351: Project.MSG_VERBOSE);
352:
353: try {
354: fos = new FileOutputStream(temporaryCommandFile);
355: PrintWriter out = new PrintWriter(
356: new BufferedOutputStream(fos));
357: //start at 1 because element 0 is the executable name
358: for (int i = 1; i < commands.length; ++i) {
359: out.println(commands[i]);
360: }
361: out.flush();
362: out.close();
363: } catch (IOException ex) {
364: throw new BuildException("saving command stream to "
365: + temporaryCommandFile, ex);
366: }
367:
368: String[] newCommandLine = new String[2];
369: newCommandLine[0] = commands[0];
370: newCommandLine[1] = "@"
371: + temporaryCommandFile.getAbsolutePath();
372: logVerbose(Commandline.describeCommand(newCommandLine));
373: executable.setCommandline(newCommandLine);
374: }
375: }
376:
377: /**
378: * scan through one fileset for files to include
379: * @param scanner the directory scanner to use.
380: * @param filesToBuild the map to place the files.
381: * @param outputTimestamp timestamp to compare against
382: * @return #of files out of date
383: * @todo should FAT granularity be included here?
384: */
385: public int scanOneFileset(DirectoryScanner scanner,
386: Hashtable filesToBuild, long outputTimestamp) {
387: int filesOutOfDate = 0;
388: String[] dependencies = scanner.getIncludedFiles();
389: File base = scanner.getBasedir();
390: //add to the list
391: for (int i = 0; i < dependencies.length; i++) {
392: File targetFile = new File(base, dependencies[i]);
393: if (filesToBuild.get(targetFile) == null) {
394: filesToBuild.put(targetFile, targetFile);
395: if (targetFile.lastModified() > outputTimestamp) {
396: filesOutOfDate++;
397: owner.log(
398: targetFile.toString() + " is out of date",
399: Project.MSG_VERBOSE);
400: } else {
401: owner.log(targetFile.toString(),
402: Project.MSG_VERBOSE);
403: }
404: }
405: }
406: return filesOutOfDate;
407: }
408: }
|