1. /*
  2. * Copyright 2001-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.pvcs;
  18. import java.io.BufferedReader;
  19. import java.io.BufferedWriter;
  20. import java.io.File;
  21. import java.io.FileNotFoundException;
  22. import java.io.FileOutputStream;
  23. import java.io.FileReader;
  24. import java.io.FileWriter;
  25. import java.io.IOException;
  26. import java.text.MessageFormat;
  27. import java.text.ParseException;
  28. import java.util.Enumeration;
  29. import java.util.Random;
  30. import java.util.Vector;
  31. import org.apache.tools.ant.BuildException;
  32. import org.apache.tools.ant.Project;
  33. import org.apache.tools.ant.taskdefs.Execute;
  34. import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
  35. import org.apache.tools.ant.taskdefs.LogOutputStream;
  36. import org.apache.tools.ant.taskdefs.LogStreamHandler;
  37. import org.apache.tools.ant.taskdefs.PumpStreamHandler;
  38. import org.apache.tools.ant.types.Commandline;
  39. /**
  40. *
  41. * Extracts the latest edition of the source code from a PVCS repository.
  42. * PVCS is a version control system
  43. * developed by <a href="http://www.merant.com/products/pvcs">Merant</a>.
  44. * <br>
  45. * Before using this tag, the user running ant must have access to the commands
  46. * of PVCS (get and pcli) and must have access to the repository. Note that the way to specify
  47. * the repository is platform dependent so use property to specify location of repository.
  48. * <br>
  49. * This version has been tested agains PVCS version 6.5 and 6.6 under Windows and Solaris.
  50. *
  51. * <b>19-04-2001</b> <p>The task now has a more robust
  52. * parser. It allows for platform independant file paths
  53. * and supports file names with <i>()</i>. Thanks to Erik Husby for
  54. * bringing the bug to my attention.
  55. *
  56. * <b>27-04-2001</b> <p>UNC paths are now handled properly.
  57. * Fix provided by Don Jeffery. He also added an <i>UpdateOnly</i> flag
  58. * that, when true, conditions the PVCS get using the -U option to only
  59. * update those files that have a modification time (in PVCS) that is newer
  60. * than the existing workfile.
  61. *
  62. * <b>25-10-2002</b> <p>Added a revision attribute that currently is a
  63. * synonym for label, but in a future release the behavior of the label
  64. * attribute will change to use the -v option of GET. See bug #13847 for
  65. * discussion.
  66. *
  67. */
  68. public class Pvcs extends org.apache.tools.ant.Task {
  69. private String pvcsbin;
  70. private String repository;
  71. private String pvcsProject;
  72. private Vector pvcsProjects;
  73. private String workspace;
  74. private String force;
  75. private String promotiongroup;
  76. private String label;
  77. private String revision;
  78. private boolean ignorerc;
  79. private boolean updateOnly;
  80. private String filenameFormat;
  81. private String lineStart;
  82. private String userId;
  83. private String config;
  84. /**
  85. * Constant for the thing to execute
  86. */
  87. private static final String PCLI_EXE = "pcli";
  88. /*
  89. * Constant for the PCLI listversionedfiles recursive i a format "get" understands
  90. */
  91. // private static final String PCLI_LVF_ARGS = "lvf -z -aw";
  92. /**
  93. * Constant for the thing to execute
  94. */
  95. private static final String GET_EXE = "get";
  96. protected int runCmd(Commandline cmd, ExecuteStreamHandler out) {
  97. try {
  98. Project aProj = getProject();
  99. Execute exe = new Execute(out);
  100. exe.setAntRun(aProj);
  101. exe.setWorkingDirectory(aProj.getBaseDir());
  102. exe.setCommandline(cmd.getCommandline());
  103. return exe.execute();
  104. } catch (java.io.IOException e) {
  105. String msg = "Failed executing: " + cmd.toString()
  106. + ". Exception: " + e.getMessage();
  107. throw new BuildException(msg, getLocation());
  108. }
  109. }
  110. private String getExecutable(String exe) {
  111. StringBuffer correctedExe = new StringBuffer();
  112. if (getPvcsbin() != null) {
  113. if (pvcsbin.endsWith(File.separator)) {
  114. correctedExe.append(pvcsbin);
  115. } else {
  116. correctedExe.append(pvcsbin).append(File.separator);
  117. }
  118. }
  119. return correctedExe.append(exe).toString();
  120. }
  121. /**
  122. * @exception org.apache.tools.ant.BuildException Something is stopping the build...
  123. */
  124. public void execute() throws org.apache.tools.ant.BuildException {
  125. int result = 0;
  126. if (repository == null || repository.trim().equals("")) {
  127. throw new BuildException("Required argument repository not specified");
  128. }
  129. // Check workspace exists
  130. // Launch PCLI listversionedfiles -z -aw
  131. // Capture output
  132. // build the command line from what we got the format is
  133. Commandline commandLine = new Commandline();
  134. commandLine.setExecutable(getExecutable(PCLI_EXE));
  135. commandLine.createArgument().setValue("lvf");
  136. commandLine.createArgument().setValue("-z");
  137. commandLine.createArgument().setValue("-aw");
  138. if (getWorkspace() != null) {
  139. commandLine.createArgument().setValue("-sp" + getWorkspace());
  140. }
  141. commandLine.createArgument().setValue("-pr" + getRepository());
  142. String uid = getUserId();
  143. if (uid != null) {
  144. commandLine.createArgument().setValue("-id" + uid);
  145. }
  146. // default pvcs project is "/"
  147. if (getPvcsproject() == null && getPvcsprojects().isEmpty()) {
  148. pvcsProject = "/";
  149. }
  150. if (getPvcsproject() != null) {
  151. commandLine.createArgument().setValue(getPvcsproject());
  152. }
  153. if (!getPvcsprojects().isEmpty()) {
  154. Enumeration e = getPvcsprojects().elements();
  155. while (e.hasMoreElements()) {
  156. String projectName = ((PvcsProject) e.nextElement()).getName();
  157. if (projectName == null || (projectName.trim()).equals("")) {
  158. throw new BuildException("name is a required attribute "
  159. + "of pvcsproject");
  160. }
  161. commandLine.createArgument().setValue(projectName);
  162. }
  163. }
  164. File tmp = null;
  165. File tmp2 = null;
  166. try {
  167. Random rand = new Random(System.currentTimeMillis());
  168. tmp = new File("pvcs_ant_" + rand.nextLong() + ".log");
  169. FileOutputStream fos = new FileOutputStream(tmp);
  170. tmp2 = new File("pvcs_ant_" + rand.nextLong() + ".log");
  171. log(commandLine.describeCommand(), Project.MSG_VERBOSE);
  172. try {
  173. result = runCmd(commandLine,
  174. new PumpStreamHandler(fos,
  175. new LogOutputStream(this,
  176. Project.MSG_WARN)));
  177. } finally {
  178. fos.close();
  179. }
  180. if (Execute.isFailure(result) && !ignorerc) {
  181. String msg = "Failed executing: " + commandLine.toString();
  182. throw new BuildException(msg, getLocation());
  183. }
  184. if (!tmp.exists()) {
  185. throw new BuildException("Communication between ant and pvcs "
  186. + "failed. No output generated from executing PVCS "
  187. + "commandline interface \"pcli\" and \"get\"");
  188. }
  189. // Create folders in workspace
  190. log("Creating folders", Project.MSG_INFO);
  191. createFolders(tmp);
  192. // Massage PCLI lvf output transforming '\' to '/' so get command works appropriately
  193. massagePCLI(tmp, tmp2);
  194. // Launch get on output captured from PCLI lvf
  195. commandLine.clearArgs();
  196. commandLine.setExecutable(getExecutable(GET_EXE));
  197. if (getConfig() != null && getConfig().length() > 0) {
  198. commandLine.createArgument().setValue("-c" + getConfig());
  199. }
  200. if (getForce() != null && getForce().equals("yes")) {
  201. commandLine.createArgument().setValue("-Y");
  202. } else {
  203. commandLine.createArgument().setValue("-N");
  204. }
  205. if (getPromotiongroup() != null) {
  206. commandLine.createArgument().setValue("-G"
  207. + getPromotiongroup());
  208. } else {
  209. if (getLabel() != null) {
  210. commandLine.createArgument().setValue("-r" + getLabel());
  211. } else {
  212. if (getRevision() != null) {
  213. commandLine.createArgument().setValue("-r"
  214. + getRevision());
  215. }
  216. }
  217. }
  218. if (updateOnly) {
  219. commandLine.createArgument().setValue("-U");
  220. }
  221. commandLine.createArgument().setValue("@" + tmp2.getAbsolutePath());
  222. log("Getting files", Project.MSG_INFO);
  223. log("Executing " + commandLine.toString(), Project.MSG_VERBOSE);
  224. result = runCmd(commandLine,
  225. new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN));
  226. if (result != 0 && !ignorerc) {
  227. String msg = "Failed executing: " + commandLine.toString()
  228. + ". Return code was " + result;
  229. throw new BuildException(msg, getLocation());
  230. }
  231. } catch (FileNotFoundException e) {
  232. String msg = "Failed executing: " + commandLine.toString()
  233. + ". Exception: " + e.getMessage();
  234. throw new BuildException(msg, getLocation());
  235. } catch (IOException e) {
  236. String msg = "Failed executing: " + commandLine.toString()
  237. + ". Exception: " + e.getMessage();
  238. throw new BuildException(msg, getLocation());
  239. } catch (ParseException e) {
  240. String msg = "Failed executing: " + commandLine.toString()
  241. + ". Exception: " + e.getMessage();
  242. throw new BuildException(msg, getLocation());
  243. } finally {
  244. if (tmp != null) {
  245. tmp.delete();
  246. }
  247. if (tmp2 != null) {
  248. tmp2.delete();
  249. }
  250. }
  251. }
  252. /**
  253. * Parses the file and creates the folders specified in the output section
  254. */
  255. private void createFolders(File file) throws IOException, ParseException {
  256. BufferedReader in = null;
  257. try {
  258. in = new BufferedReader(new FileReader(file));
  259. MessageFormat mf = new MessageFormat(getFilenameFormat());
  260. String line = in.readLine();
  261. while (line != null) {
  262. log("Considering \"" + line + "\"", Project.MSG_VERBOSE);
  263. if (line.startsWith("\"\\")
  264. || line.startsWith("\"/")
  265. || (line.length() > 3 && line.startsWith("\"")
  266. && Character.isLetter(line.charAt(1))
  267. && String.valueOf(line.charAt(2)).equals(":")
  268. && String.valueOf(line.charAt(3)).equals("\\"))) {
  269. Object[] objs = mf.parse(line);
  270. String f = (String) objs[1];
  271. // Extract the name of the directory from the filename
  272. int index = f.lastIndexOf(File.separator);
  273. if (index > -1) {
  274. File dir = new File(f.substring(0, index));
  275. if (!dir.exists()) {
  276. log("Creating " + dir.getAbsolutePath(),
  277. Project.MSG_VERBOSE);
  278. if (dir.mkdirs()) {
  279. log("Created " + dir.getAbsolutePath(),
  280. Project.MSG_INFO);
  281. } else {
  282. log("Failed to create "
  283. + dir.getAbsolutePath(),
  284. Project.MSG_INFO);
  285. }
  286. } else {
  287. log(dir.getAbsolutePath() + " exists. Skipping",
  288. Project.MSG_VERBOSE);
  289. }
  290. } else {
  291. log("File separator problem with " + line,
  292. Project.MSG_WARN);
  293. }
  294. } else {
  295. log("Skipped \"" + line + "\"", Project.MSG_VERBOSE);
  296. }
  297. line = in.readLine();
  298. }
  299. } finally {
  300. if (in != null) {
  301. in.close();
  302. }
  303. }
  304. }
  305. /**
  306. * Simple hack to handle the PVCS command-line tools botch when
  307. * handling UNC notation.
  308. */
  309. private void massagePCLI(File in, File out)
  310. throws FileNotFoundException, IOException {
  311. BufferedReader inReader = null;
  312. BufferedWriter outWriter = null;
  313. try {
  314. inReader = new BufferedReader(new FileReader(in));
  315. outWriter = new BufferedWriter(new FileWriter(out));
  316. String s = null;
  317. while ((s = inReader.readLine()) != null) {
  318. String sNormal = s.replace('\\', '/');
  319. outWriter.write(sNormal);
  320. outWriter.newLine();
  321. }
  322. } finally {
  323. if (inReader != null) {
  324. inReader.close();
  325. }
  326. if (outWriter != null) {
  327. outWriter.close();
  328. }
  329. }
  330. }
  331. /**
  332. * Get network name of the PVCS repository
  333. * @return String
  334. */
  335. public String getRepository() {
  336. return repository;
  337. }
  338. /**
  339. * The filenameFormat attribute defines a MessageFormat string used
  340. * to parse the output of the pcli command. It defaults to
  341. * <code>{0}-arc({1})</code>. Repositories where the archive
  342. * extension is not -arc should set this.
  343. */
  344. public String getFilenameFormat() {
  345. return filenameFormat;
  346. }
  347. /**
  348. * The format of the folder names; optional.
  349. * This must be in a format suitable for
  350. * <code>java.text.MessageFormat</code>.
  351. * Index 1 of the format will be used as the file name.
  352. * Defaults to <code>{0}-arc({1})</code>
  353. */
  354. public void setFilenameFormat(String f) {
  355. filenameFormat = f;
  356. }
  357. /**
  358. * The lineStart attribute is used to parse the output of the pcli
  359. * command. It defaults to <code>"P:</code>. The parser already
  360. * knows about / and \\, this property is useful in cases where the
  361. * repository is accessed on a Windows platform via a drive letter
  362. * mapping.
  363. */
  364. public String getLineStart() {
  365. return lineStart;
  366. }
  367. /**
  368. * What a valid return value from PVCS looks like
  369. * when it describes a file. Defaults to <code>"P:</code>.
  370. * If you are not using an UNC name for your repository and the
  371. * drive letter <code>P</code> is incorrect for your setup, you may
  372. * need to change this value, UNC names will always be
  373. * accepted.
  374. */
  375. public void setLineStart(String l) {
  376. lineStart = l;
  377. }
  378. /**
  379. * The network name of the PVCS repository; required.
  380. * @param repo String
  381. */
  382. public void setRepository(String repo) {
  383. repository = repo;
  384. }
  385. /**
  386. * Get name of the project in the PVCS repository
  387. * @return String
  388. */
  389. public String getPvcsproject() {
  390. return pvcsProject;
  391. }
  392. /**
  393. * The project within the PVCS repository to extract files from;
  394. * optional, default "/"
  395. * @param prj String
  396. */
  397. public void setPvcsproject(String prj) {
  398. pvcsProject = prj;
  399. }
  400. /**
  401. * Get name of the project in the PVCS repository
  402. * @return Vector
  403. */
  404. public Vector getPvcsprojects() {
  405. return pvcsProjects;
  406. }
  407. /**
  408. * Get name of the workspace to store the retrieved files
  409. * @return String
  410. */
  411. public String getWorkspace() {
  412. return workspace;
  413. }
  414. /**
  415. * Workspace to use; optional.
  416. * By specifying a workspace, the files are extracted to that location.
  417. * A PVCS workspace is a name for a location of the workfiles and
  418. * isn't as such the location itself.
  419. * You define the location for a workspace using the PVCS GUI clients.
  420. * If this isn't specified the default workspace for the current user is used.
  421. * @param ws String
  422. */
  423. public void setWorkspace(String ws) {
  424. workspace = ws;
  425. }
  426. /**
  427. * Get name of the PVCS bin directory
  428. * @return String
  429. */
  430. public String getPvcsbin() {
  431. return pvcsbin;
  432. }
  433. /**
  434. * Specifies the location of the PVCS bin directory; optional if on the PATH.
  435. * On some systems the PVCS executables <i>pcli</i>
  436. * and <i>get</i> are not found in the PATH. In such cases this attribute
  437. * should be set to the bin directory of the PVCS installation containing
  438. * the executables mentioned before. If this attribute isn't specified the
  439. * tag expects the executables to be found using the PATH environment variable.
  440. * @param bin PVCS bin directory
  441. * @todo use a File setter and resolve paths.
  442. */
  443. public void setPvcsbin(String bin) {
  444. pvcsbin = bin;
  445. }
  446. /**
  447. * Get value of force
  448. * @return String
  449. */
  450. public String getForce() {
  451. return force;
  452. }
  453. /**
  454. * Specifies the value of the force argument; optional.
  455. * If set to <i>yes</i> all files that exists and are
  456. * writable are overwritten. Default <i>no</i> causes the files
  457. * that are writable to be ignored. This stops the PVCS command
  458. * <i>get</i> to stop asking questions!
  459. * @todo make a boolean setter
  460. * @param f String (yes/no)
  461. */
  462. public void setForce(String f) {
  463. if (f != null && f.equalsIgnoreCase("yes")) {
  464. force = "yes";
  465. } else {
  466. force = "no";
  467. }
  468. }
  469. /**
  470. * Get value of promotiongroup
  471. * @return String
  472. */
  473. public String getPromotiongroup() {
  474. return promotiongroup;
  475. }
  476. /**
  477. * Specifies the name of the promotiongroup argument
  478. * @param w String
  479. */
  480. public void setPromotiongroup(String w) {
  481. promotiongroup = w;
  482. }
  483. /**
  484. * Get value of label
  485. * @return String
  486. */
  487. public String getLabel() {
  488. return label;
  489. }
  490. /**
  491. * Only files marked with this label are extracted; optional.
  492. * @param l String
  493. */
  494. public void setLabel(String l) {
  495. label = l;
  496. }
  497. /**
  498. * Get value of revision
  499. * @return String
  500. */
  501. public String getRevision() {
  502. return revision;
  503. }
  504. /**
  505. * Only files with this revision are extract; optional.
  506. * @param r String
  507. */
  508. public void setRevision(String r) {
  509. revision = r;
  510. }
  511. /**
  512. * Get value of ignorereturncode
  513. * @return String
  514. */
  515. public boolean getIgnoreReturnCode() {
  516. return ignorerc;
  517. }
  518. /**
  519. * If set to true the return value from executing the pvcs
  520. * commands are ignored; optional, default false.
  521. */
  522. public void setIgnoreReturnCode(boolean b) {
  523. ignorerc = b;
  524. }
  525. /**
  526. * Specify a project within the PVCS repository to extract files from.
  527. * @param p
  528. */
  529. public void addPvcsproject(PvcsProject p) {
  530. pvcsProjects.addElement(p);
  531. }
  532. public boolean getUpdateOnly() {
  533. return updateOnly;
  534. }
  535. /**
  536. * If set to <i>true</i> files are fetched only if
  537. * newer than existing local files; optional, default false.
  538. */
  539. public void setUpdateOnly(boolean l) {
  540. updateOnly = l;
  541. }
  542. /**
  543. * returns the path of the configuration file to be used
  544. * @return the path of the config file
  545. */
  546. public String getConfig() {
  547. return config;
  548. }
  549. /**
  550. * Sets a configuration file other than the default to be used.
  551. * These files have a .cfg extension and are often found in archive or pvcsprop folders.
  552. * @param f config file - can be given absolute or relative to ant basedir
  553. */
  554. public void setConfig(File f) {
  555. config = f.toString();
  556. }
  557. public String getUserId() {
  558. return userId;
  559. }
  560. /**
  561. * User ID; unused.
  562. * @ant.attribute ignore="true"
  563. */
  564. public void setUserId(String u) {
  565. userId = u;
  566. }
  567. /**
  568. * Creates a Pvcs object
  569. */
  570. public Pvcs() {
  571. super();
  572. pvcsProject = null;
  573. pvcsProjects = new Vector();
  574. workspace = null;
  575. repository = null;
  576. pvcsbin = null;
  577. force = null;
  578. promotiongroup = null;
  579. label = null;
  580. ignorerc = false;
  581. updateOnly = false;
  582. lineStart = "\"P:";
  583. filenameFormat = "{0}-arc({1})";
  584. }
  585. }