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