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;
020:
021: import java.io.BufferedOutputStream;
022: import java.io.File;
023: import java.io.FileOutputStream;
024: import java.io.IOException;
025: import java.io.OutputStream;
026: import java.io.PrintStream;
027: import java.util.Vector;
028: import org.apache.tools.ant.BuildException;
029: import org.apache.tools.ant.Project;
030: import org.apache.tools.ant.Task;
031: import org.apache.tools.ant.types.Commandline;
032: import org.apache.tools.ant.types.Environment;
033: import org.apache.tools.ant.util.StringUtils;
034: import org.apache.tools.ant.util.FileUtils;
035:
036: /**
037: * original Cvs.java 1.20
038: *
039: * NOTE: This implementation has been moved here from Cvs.java with
040: * the addition of some accessors for extensibility. Another task
041: * can extend this with some customized output processing.
042: *
043: * @since Ant 1.5
044: */
045: public abstract class AbstractCvsTask extends Task {
046: /**
047: * Default compression level to use, if compression is enabled via
048: * setCompression( true ).
049: */
050: public static final int DEFAULT_COMPRESSION_LEVEL = 3;
051: private static final int MAXIMUM_COMRESSION_LEVEL = 9;
052:
053: private Commandline cmd = new Commandline();
054:
055: /** list of Commandline children */
056: private Vector vecCommandlines = new Vector();
057:
058: /**
059: * the CVSROOT variable.
060: */
061: private String cvsRoot;
062:
063: /**
064: * the CVS_RSH variable.
065: */
066: private String cvsRsh;
067:
068: /**
069: * the package/module to check out.
070: */
071: private String cvsPackage;
072: /**
073: * the tag
074: */
075: private String tag;
076: /**
077: * the default command.
078: */
079: private static final String DEFAULT_COMMAND = "checkout";
080: /**
081: * the CVS command to execute.
082: */
083: private String command = null;
084:
085: /**
086: * suppress information messages.
087: */
088: private boolean quiet = false;
089:
090: /**
091: * suppress all messages.
092: */
093: private boolean reallyquiet = false;
094:
095: /**
096: * compression level to use.
097: */
098: private int compression = 0;
099:
100: /**
101: * report only, don't change any files.
102: */
103: private boolean noexec = false;
104:
105: /**
106: * CVS port
107: */
108: private int port = 0;
109:
110: /**
111: * CVS password file
112: */
113: private File passFile = null;
114:
115: /**
116: * the directory where the checked out files should be placed.
117: */
118: private File dest;
119:
120: /** whether or not to append stdout/stderr to existing files */
121: private boolean append = false;
122:
123: /**
124: * the file to direct standard output from the command.
125: */
126: private File output;
127:
128: /**
129: * the file to direct standard error from the command.
130: */
131: private File error;
132:
133: /**
134: * If true it will stop the build if cvs exits with error.
135: * Default is false. (Iulian)
136: */
137: private boolean failOnError = false;
138:
139: /**
140: * Create accessors for the following, to allow different handling of
141: * the output.
142: */
143: private ExecuteStreamHandler executeStreamHandler;
144: private OutputStream outputStream;
145: private OutputStream errorStream;
146:
147: /** empty no-arg constructor*/
148: public AbstractCvsTask() {
149: super ();
150: }
151:
152: /**
153: * sets the handler
154: * @param handler a handler able of processing the output and error streams from the cvs exe
155: */
156: public void setExecuteStreamHandler(ExecuteStreamHandler handler) {
157: this .executeStreamHandler = handler;
158: }
159:
160: /**
161: * find the handler and instantiate it if it does not exist yet
162: * @return handler for output and error streams
163: */
164: protected ExecuteStreamHandler getExecuteStreamHandler() {
165:
166: if (this .executeStreamHandler == null) {
167: setExecuteStreamHandler(new PumpStreamHandler(
168: getOutputStream(), getErrorStream()));
169: }
170:
171: return this .executeStreamHandler;
172: }
173:
174: /**
175: * sets a stream to which the output from the cvs executable should be sent
176: * @param outputStream stream to which the stdout from cvs should go
177: */
178: protected void setOutputStream(OutputStream outputStream) {
179:
180: this .outputStream = outputStream;
181: }
182:
183: /**
184: * access the stream to which the stdout from cvs should go
185: * if this stream has already been set, it will be returned
186: * if the stream has not yet been set, if the attribute output
187: * has been set, the output stream will go to the output file
188: * otherwise the output will go to ant's logging system
189: * @return output stream to which cvs' stdout should go to
190: */
191: protected OutputStream getOutputStream() {
192:
193: if (this .outputStream == null) {
194:
195: if (output != null) {
196: try {
197: setOutputStream(new PrintStream(
198: new BufferedOutputStream(
199: new FileOutputStream(output
200: .getPath(), append))));
201: } catch (IOException e) {
202: throw new BuildException(e, getLocation());
203: }
204: } else {
205: setOutputStream(new LogOutputStream(this ,
206: Project.MSG_INFO));
207: }
208: }
209:
210: return this .outputStream;
211: }
212:
213: /**
214: * sets a stream to which the stderr from the cvs exe should go
215: * @param errorStream an output stream willing to process stderr
216: */
217: protected void setErrorStream(OutputStream errorStream) {
218:
219: this .errorStream = errorStream;
220: }
221:
222: /**
223: * access the stream to which the stderr from cvs should go
224: * if this stream has already been set, it will be returned
225: * if the stream has not yet been set, if the attribute error
226: * has been set, the output stream will go to the file denoted by the error attribute
227: * otherwise the stderr output will go to ant's logging system
228: * @return output stream to which cvs' stderr should go to
229: */
230: protected OutputStream getErrorStream() {
231:
232: if (this .errorStream == null) {
233:
234: if (error != null) {
235:
236: try {
237: setErrorStream(new PrintStream(
238: new BufferedOutputStream(
239: new FileOutputStream(error
240: .getPath(), append))));
241: } catch (IOException e) {
242: throw new BuildException(e, getLocation());
243: }
244: } else {
245: setErrorStream(new LogOutputStream(this ,
246: Project.MSG_WARN));
247: }
248: }
249:
250: return this .errorStream;
251: }
252:
253: /**
254: * Sets up the environment for toExecute and then runs it.
255: * @param toExecute the command line to execute
256: * @throws BuildException if failonError is set to true and the cvs command fails
257: */
258: protected void runCommand(Commandline toExecute)
259: throws BuildException {
260: // XXX: we should use JCVS (www.ice.com/JCVS) instead of
261: // command line execution so that we don't rely on having
262: // native CVS stuff around (SM)
263:
264: // We can't do it ourselves as jCVS is GPLed, a third party task
265: // outside of jakarta repositories would be possible though (SB).
266:
267: Environment env = new Environment();
268:
269: if (port > 0) {
270: Environment.Variable var = new Environment.Variable();
271: var.setKey("CVS_CLIENT_PORT");
272: var.setValue(String.valueOf(port));
273: env.addVariable(var);
274: }
275:
276: /**
277: * Need a better cross platform integration with <cvspass>, so
278: * use the same filename.
279: */
280: if (passFile == null) {
281:
282: File defaultPassFile = new File(System
283: .getProperty("cygwin.user.home", System
284: .getProperty("user.home"))
285: + File.separatorChar + ".cvspass");
286:
287: if (defaultPassFile.exists()) {
288: this .setPassfile(defaultPassFile);
289: }
290: }
291:
292: if (passFile != null) {
293: if (passFile.isFile() && passFile.canRead()) {
294: Environment.Variable var = new Environment.Variable();
295: var.setKey("CVS_PASSFILE");
296: var.setValue(String.valueOf(passFile));
297: env.addVariable(var);
298: log("Using cvs passfile: " + String.valueOf(passFile),
299: Project.MSG_VERBOSE);
300: } else if (!passFile.canRead()) {
301: log("cvs passfile: " + String.valueOf(passFile)
302: + " ignored as it is not readable",
303: Project.MSG_WARN);
304: } else {
305: log("cvs passfile: " + String.valueOf(passFile)
306: + " ignored as it is not a file",
307: Project.MSG_WARN);
308: }
309: }
310:
311: if (cvsRsh != null) {
312: Environment.Variable var = new Environment.Variable();
313: var.setKey("CVS_RSH");
314: var.setValue(String.valueOf(cvsRsh));
315: env.addVariable(var);
316: }
317:
318: //
319: // Just call the getExecuteStreamHandler() and let it handle
320: // the semantics of instantiation or retrieval.
321: //
322: Execute exe = new Execute(getExecuteStreamHandler(), null);
323:
324: exe.setAntRun(getProject());
325: if (dest == null) {
326: dest = getProject().getBaseDir();
327: }
328:
329: if (!dest.exists()) {
330: dest.mkdirs();
331: }
332:
333: exe.setWorkingDirectory(dest);
334: exe.setCommandline(toExecute.getCommandline());
335: exe.setEnvironment(env.getVariables());
336:
337: try {
338: String actualCommandLine = executeToString(exe);
339: log(actualCommandLine, Project.MSG_VERBOSE);
340: int retCode = exe.execute();
341: log("retCode=" + retCode, Project.MSG_DEBUG);
342: /*Throw an exception if cvs exited with error. (Iulian)*/
343: if (failOnError && Execute.isFailure(retCode)) {
344: throw new BuildException("cvs exited with error code "
345: + retCode + StringUtils.LINE_SEP
346: + "Command line was [" + actualCommandLine
347: + "]", getLocation());
348: }
349: } catch (IOException e) {
350: if (failOnError) {
351: throw new BuildException(e, getLocation());
352: }
353: log("Caught exception: " + e.getMessage(), Project.MSG_WARN);
354: } catch (BuildException e) {
355: if (failOnError) {
356: throw (e);
357: }
358: Throwable t = e.getException();
359: if (t == null) {
360: t = e;
361: }
362: log("Caught exception: " + t.getMessage(), Project.MSG_WARN);
363: } catch (Exception e) {
364: if (failOnError) {
365: throw new BuildException(e, getLocation());
366: }
367: log("Caught exception: " + e.getMessage(), Project.MSG_WARN);
368: }
369: }
370:
371: /**
372: * do the work
373: * @throws BuildException if failonerror is set to true and the
374: * cvs command fails.
375: */
376: public void execute() throws BuildException {
377:
378: String savedCommand = getCommand();
379:
380: if (this .getCommand() == null && vecCommandlines.size() == 0) {
381: // re-implement legacy behaviour:
382: this .setCommand(AbstractCvsTask.DEFAULT_COMMAND);
383: }
384:
385: String c = this .getCommand();
386: Commandline cloned = null;
387: if (c != null) {
388: cloned = (Commandline) cmd.clone();
389: cloned.createArgument(true).setLine(c);
390: this .addConfiguredCommandline(cloned, true);
391: }
392:
393: try {
394: for (int i = 0; i < vecCommandlines.size(); i++) {
395: this .runCommand((Commandline) vecCommandlines
396: .elementAt(i));
397: }
398: } finally {
399: if (cloned != null) {
400: removeCommandline(cloned);
401: }
402: setCommand(savedCommand);
403: FileUtils.close(outputStream);
404: FileUtils.close(errorStream);
405: }
406: }
407:
408: private String executeToString(Execute execute) {
409:
410: StringBuffer stringBuffer = new StringBuffer(Commandline
411: .describeCommand(execute.getCommandline()));
412:
413: String newLine = StringUtils.LINE_SEP;
414: String[] variableArray = execute.getEnvironment();
415:
416: if (variableArray != null) {
417: stringBuffer.append(newLine);
418: stringBuffer.append(newLine);
419: stringBuffer.append("environment:");
420: stringBuffer.append(newLine);
421: for (int z = 0; z < variableArray.length; z++) {
422: stringBuffer.append(newLine);
423: stringBuffer.append("\t");
424: stringBuffer.append(variableArray[z]);
425: }
426: }
427:
428: return stringBuffer.toString();
429: }
430:
431: /**
432: * The CVSROOT variable.
433: *
434: * @param root the CVSROOT variable
435: */
436: public void setCvsRoot(String root) {
437:
438: // Check if not real cvsroot => set it to null
439: if (root != null) {
440: if (root.trim().equals("")) {
441: root = null;
442: }
443: }
444:
445: this .cvsRoot = root;
446: }
447:
448: /**
449: * access the CVSROOT variable
450: * @return CVSROOT
451: */
452: public String getCvsRoot() {
453:
454: return this .cvsRoot;
455: }
456:
457: /**
458: * The CVS_RSH variable.
459: *
460: * @param rsh the CVS_RSH variable
461: */
462: public void setCvsRsh(String rsh) {
463: // Check if not real cvsrsh => set it to null
464: if (rsh != null) {
465: if (rsh.trim().equals("")) {
466: rsh = null;
467: }
468: }
469:
470: this .cvsRsh = rsh;
471: }
472:
473: /**
474: * access the CVS_RSH variable
475: * @return the CVS_RSH variable
476: */
477: public String getCvsRsh() {
478:
479: return this .cvsRsh;
480: }
481:
482: /**
483: * Port used by CVS to communicate with the server.
484: *
485: * @param port port of CVS
486: */
487: public void setPort(int port) {
488: this .port = port;
489: }
490:
491: /**
492: * access the port of CVS
493: * @return the port of CVS
494: */
495: public int getPort() {
496:
497: return this .port;
498: }
499:
500: /**
501: * Password file to read passwords from.
502: *
503: * @param passFile password file to read passwords from
504: */
505: public void setPassfile(File passFile) {
506: this .passFile = passFile;
507: }
508:
509: /**
510: * find the password file
511: * @return password file
512: */
513: public File getPassFile() {
514:
515: return this .passFile;
516: }
517:
518: /**
519: * The directory where the checked out files should be placed.
520: *
521: * <p>Note that this is different from CVS's -d command line
522: * switch as Ant will never shorten pathnames to avoid empty
523: * directories.</p>
524: *
525: * @param dest directory where the checked out files should be placed
526: */
527: public void setDest(File dest) {
528: this .dest = dest;
529: }
530:
531: /**
532: * get the file where the checked out files should be placed
533: *
534: * @return directory where the checked out files should be placed
535: */
536: public File getDest() {
537:
538: return this .dest;
539: }
540:
541: /**
542: * The package/module to operate upon.
543: *
544: * @param p package or module to operate upon
545: */
546: public void setPackage(String p) {
547: this .cvsPackage = p;
548: }
549:
550: /**
551: * access the package or module to operate upon
552: *
553: * @return package/module
554: */
555: public String getPackage() {
556:
557: return this .cvsPackage;
558: }
559:
560: /**
561: * tag or branch
562: * @return tag or branch
563: * @since ant 1.6.1
564: */
565: public String getTag() {
566: return tag;
567: }
568:
569: /**
570: * The tag of the package/module to operate upon.
571: * @param p tag
572: */
573: public void setTag(String p) {
574: // Check if not real tag => set it to null
575: if (p != null && p.trim().length() > 0) {
576: tag = p;
577: addCommandArgument("-r" + p);
578: }
579: }
580:
581: /**
582: * This needs to be public to allow configuration
583: * of commands externally.
584: * @param arg command argument
585: */
586: public void addCommandArgument(String arg) {
587: this .addCommandArgument(cmd, arg);
588: }
589:
590: /**
591: * This method adds a command line argument to an external command.
592: *
593: * I do not understand what this method does in this class ???
594: * particularly not why it is public ????
595: * AntoineLL July 23d 2003
596: *
597: * @param c command line to which one argument should be added
598: * @param arg argument to add
599: */
600: public void addCommandArgument(Commandline c, String arg) {
601: c.createArgument().setValue(arg);
602: }
603:
604: /**
605: * Use the most recent revision no later than the given date.
606: * @param p a date as string in a format that the CVS executable
607: * can understand see man cvs
608: */
609: public void setDate(String p) {
610: if (p != null && p.trim().length() > 0) {
611: addCommandArgument("-D");
612: addCommandArgument(p);
613: }
614: }
615:
616: /**
617: * The CVS command to execute.
618: *
619: * This should be deprecated, it is better to use the Commandline class ?
620: * AntoineLL July 23d 2003
621: *
622: * @param c a command as string
623: */
624: public void setCommand(String c) {
625: this .command = c;
626: }
627:
628: /**
629: * accessor to a command line as string
630: *
631: * This should be deprecated
632: * AntoineLL July 23d 2003
633: *
634: * @return command line as string
635: */
636: public String getCommand() {
637: return this .command;
638: }
639:
640: /**
641: * If true, suppress informational messages.
642: * @param q if true, suppress informational messages
643: */
644: public void setQuiet(boolean q) {
645: quiet = q;
646: }
647:
648: /**
649: * If true, suppress all messages.
650: * @param q if true, suppress all messages
651: * @since Ant 1.6
652: */
653: public void setReallyquiet(boolean q) {
654: reallyquiet = q;
655: }
656:
657: /**
658: * If true, report only and don't change any files.
659: *
660: * @param ne if true, report only and do not change any files.
661: */
662: public void setNoexec(boolean ne) {
663: noexec = ne;
664: }
665:
666: /**
667: * The file to direct standard output from the command.
668: * @param output a file to which stdout should go
669: */
670: public void setOutput(File output) {
671: this .output = output;
672: }
673:
674: /**
675: * The file to direct standard error from the command.
676: *
677: * @param error a file to which stderr should go
678: */
679: public void setError(File error) {
680: this .error = error;
681: }
682:
683: /**
684: * Whether to append output/error when redirecting to a file.
685: * @param value true indicated you want to append
686: */
687: public void setAppend(boolean value) {
688: this .append = value;
689: }
690:
691: /**
692: * Stop the build process if the command exits with
693: * a return code other than 0.
694: * Defaults to false.
695: * @param failOnError stop the build process if the command exits with
696: * a return code other than 0
697: */
698: public void setFailOnError(boolean failOnError) {
699: this .failOnError = failOnError;
700: }
701:
702: /**
703: * Configure a commandline element for things like cvsRoot, quiet, etc.
704: * @param c the command line which will be configured
705: * if the commandline is initially null, the function is a noop
706: * otherwise the function append to the commandline arguments concerning
707: * <ul>
708: * <li>
709: * cvs package
710: * </li>
711: * <li>
712: * compression
713: * </li>
714: * <li>
715: * quiet or reallyquiet
716: * </li>
717: * <li>cvsroot</li>
718: * <li>noexec</li>
719: * </ul>
720: */
721: protected void configureCommandline(Commandline c) {
722: if (c == null) {
723: return;
724: }
725: c.setExecutable("cvs");
726: if (cvsPackage != null) {
727: c.createArgument().setLine(cvsPackage);
728: }
729: if (this .compression > 0
730: && this .compression <= MAXIMUM_COMRESSION_LEVEL) {
731: c.createArgument(true).setValue("-z" + this .compression);
732: }
733: if (quiet && !reallyquiet) {
734: c.createArgument(true).setValue("-q");
735: }
736: if (reallyquiet) {
737: c.createArgument(true).setValue("-Q");
738: }
739: if (noexec) {
740: c.createArgument(true).setValue("-n");
741: }
742: if (cvsRoot != null) {
743: c.createArgument(true).setLine("-d" + cvsRoot);
744: }
745: }
746:
747: /**
748: * remove a particular command from a vector of command lines
749: * @param c command line which should be removed
750: */
751: protected void removeCommandline(Commandline c) {
752: vecCommandlines.removeElement(c);
753: }
754:
755: /**
756: * Adds direct command-line to execute.
757: * @param c command line to execute
758: */
759: public void addConfiguredCommandline(Commandline c) {
760: this .addConfiguredCommandline(c, false);
761: }
762:
763: /**
764: * Configures and adds the given Commandline.
765: * @param c commandline to insert
766: * @param insertAtStart If true, c is
767: * inserted at the beginning of the vector of command lines
768: */
769: public void addConfiguredCommandline(Commandline c,
770: boolean insertAtStart) {
771: if (c == null) {
772: return;
773: }
774: this .configureCommandline(c);
775: if (insertAtStart) {
776: vecCommandlines.insertElementAt(c, 0);
777: } else {
778: vecCommandlines.addElement(c);
779: }
780: }
781:
782: /**
783: * If set to a value 1-9 it adds -zN to the cvs command line, else
784: * it disables compression.
785: * @param level compression level 1 to 9
786: */
787: public void setCompressionLevel(int level) {
788: this .compression = level;
789: }
790:
791: /**
792: * If true, this is the same as compressionlevel="3".
793: *
794: * @param usecomp If true, turns on compression using default
795: * level, AbstractCvsTask.DEFAULT_COMPRESSION_LEVEL.
796: */
797: public void setCompression(boolean usecomp) {
798: setCompressionLevel(usecomp ? AbstractCvsTask.DEFAULT_COMPRESSION_LEVEL
799: : 0);
800: }
801:
802: }
|