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: package org.apache.tools.ant.taskdefs.optional.pvcs;
019:
020: import java.io.BufferedReader;
021: import java.io.BufferedWriter;
022: import java.io.File;
023: import java.io.FileNotFoundException;
024: import java.io.FileOutputStream;
025: import java.io.FileReader;
026: import java.io.FileWriter;
027: import java.io.IOException;
028: import java.text.MessageFormat;
029: import java.text.ParseException;
030: import java.util.Enumeration;
031: import java.util.Random;
032: import java.util.Vector;
033: import org.apache.tools.ant.BuildException;
034: import org.apache.tools.ant.Project;
035: import org.apache.tools.ant.taskdefs.Execute;
036: import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
037: import org.apache.tools.ant.taskdefs.LogOutputStream;
038: import org.apache.tools.ant.taskdefs.LogStreamHandler;
039: import org.apache.tools.ant.taskdefs.PumpStreamHandler;
040: import org.apache.tools.ant.types.Commandline;
041:
042: /**
043: *
044: * Extracts the latest edition of the source code from a PVCS repository.
045: * PVCS is a version control system
046: * developed by <a href="http://www.merant.com/products/pvcs">Merant</a>.
047: * <br>
048: * Before using this tag, the user running ant must have access to the commands
049: * of PVCS (get and pcli) and must have access to the repository. Note that the way to specify
050: * the repository is platform dependent so use property to specify location of repository.
051: * <br>
052: * This version has been tested agains PVCS version 6.5 and 6.6 under Windows and Solaris.
053:
054: *
055: * <b>19-04-2001</b> <p>The task now has a more robust
056: * parser. It allows for platform independant file paths
057: * and supports file names with <i>()</i>. Thanks to Erik Husby for
058: * bringing the bug to my attention.
059: *
060: * <b>27-04-2001</b> <p>UNC paths are now handled properly.
061: * Fix provided by Don Jeffery. He also added an <i>UpdateOnly</i> flag
062: * that, when true, conditions the PVCS get using the -U option to only
063: * update those files that have a modification time (in PVCS) that is newer
064: * than the existing workfile.
065: *
066: * <b>25-10-2002</b> <p>Added a revision attribute that currently is a
067: * synonym for label, but in a future release the behavior of the label
068: * attribute will change to use the -v option of GET. See bug #13847 for
069: * discussion.
070: *
071: */
072: public class Pvcs extends org.apache.tools.ant.Task {
073: // CheckStyle - magic numbers
074: // checking for "X:\ 0=dquote,1=letter,2=:,3=\
075: private static final int POS_1 = 1;
076: private static final int POS_2 = 2;
077: private static final int POS_3 = 3;
078:
079: private String pvcsbin;
080: private String repository;
081: private String pvcsProject;
082: private Vector pvcsProjects;
083: private String workspace;
084: private String force;
085: private String promotiongroup;
086: private String label;
087: private String revision;
088: private boolean ignorerc;
089: private boolean updateOnly;
090: private String filenameFormat;
091: private String lineStart;
092: private String userId;
093: private String config;
094: /**
095: * Constant for the thing to execute
096: */
097: private static final String PCLI_EXE = "pcli";
098:
099: /*
100: * Constant for the PCLI listversionedfiles recursive i a format "get" understands
101: */
102: // private static final String PCLI_LVF_ARGS = "lvf -z -aw";
103: /**
104: * Constant for the thing to execute
105: */
106: private static final String GET_EXE = "get";
107:
108: /**
109: * Run the command.
110: * @param cmd the command line to use.
111: * @param out the output stream handler to use.
112: * @return the exit code of the command.
113: */
114: protected int runCmd(Commandline cmd, ExecuteStreamHandler out) {
115: try {
116: Project aProj = getProject();
117: Execute exe = new Execute(out);
118: exe.setAntRun(aProj);
119: exe.setWorkingDirectory(aProj.getBaseDir());
120: exe.setCommandline(cmd.getCommandline());
121: return exe.execute();
122: } catch (java.io.IOException e) {
123: String msg = "Failed executing: " + cmd.toString()
124: + ". Exception: " + e.getMessage();
125: throw new BuildException(msg, getLocation());
126: }
127: }
128:
129: private String getExecutable(String exe) {
130: StringBuffer correctedExe = new StringBuffer();
131: if (getPvcsbin() != null) {
132: if (pvcsbin.endsWith(File.separator)) {
133: correctedExe.append(pvcsbin);
134: } else {
135: correctedExe.append(pvcsbin).append(File.separator);
136: }
137: }
138: return correctedExe.append(exe).toString();
139: }
140:
141: /**
142: * @exception org.apache.tools.ant.BuildException Something is stopping the build...
143: */
144: public void execute() throws org.apache.tools.ant.BuildException {
145: int result = 0;
146:
147: if (repository == null || repository.trim().equals("")) {
148: throw new BuildException(
149: "Required argument repository not specified");
150: }
151:
152: // Check workspace exists
153: // Launch PCLI listversionedfiles -z -aw
154: // Capture output
155: // build the command line from what we got the format is
156: Commandline commandLine = new Commandline();
157: commandLine.setExecutable(getExecutable(PCLI_EXE));
158:
159: commandLine.createArgument().setValue("lvf");
160: commandLine.createArgument().setValue("-z");
161: commandLine.createArgument().setValue("-aw");
162: if (getWorkspace() != null) {
163: commandLine.createArgument().setValue(
164: "-sp" + getWorkspace());
165: }
166: commandLine.createArgument().setValue("-pr" + getRepository());
167:
168: String uid = getUserId();
169:
170: if (uid != null) {
171: commandLine.createArgument().setValue("-id" + uid);
172: }
173:
174: // default pvcs project is "/"
175: if (getPvcsproject() == null && getPvcsprojects().isEmpty()) {
176: pvcsProject = "/";
177: }
178:
179: if (getPvcsproject() != null) {
180: commandLine.createArgument().setValue(getPvcsproject());
181: }
182: if (!getPvcsprojects().isEmpty()) {
183: Enumeration e = getPvcsprojects().elements();
184: while (e.hasMoreElements()) {
185: String projectName = ((PvcsProject) e.nextElement())
186: .getName();
187: if (projectName == null
188: || (projectName.trim()).equals("")) {
189: throw new BuildException(
190: "name is a required attribute "
191: + "of pvcsproject");
192: }
193: commandLine.createArgument().setValue(projectName);
194: }
195: }
196:
197: File tmp = null;
198: File tmp2 = null;
199: try {
200: Random rand = new Random(System.currentTimeMillis());
201: tmp = new File("pvcs_ant_" + rand.nextLong() + ".log");
202: FileOutputStream fos = new FileOutputStream(tmp);
203: tmp2 = new File("pvcs_ant_" + rand.nextLong() + ".log");
204: log(commandLine.describeCommand(), Project.MSG_VERBOSE);
205: try {
206: result = runCmd(commandLine, new PumpStreamHandler(fos,
207: new LogOutputStream(this , Project.MSG_WARN)));
208: } finally {
209: fos.close();
210: }
211:
212: if (Execute.isFailure(result) && !ignorerc) {
213: String msg = "Failed executing: "
214: + commandLine.toString();
215: throw new BuildException(msg, getLocation());
216: }
217:
218: if (!tmp.exists()) {
219: throw new BuildException(
220: "Communication between ant and pvcs "
221: + "failed. No output generated from executing PVCS "
222: + "commandline interface \"pcli\" and \"get\"");
223: }
224:
225: // Create folders in workspace
226: log("Creating folders", Project.MSG_INFO);
227: createFolders(tmp);
228:
229: // Massage PCLI lvf output transforming '\' to '/' so get command works appropriately
230: massagePCLI(tmp, tmp2);
231:
232: // Launch get on output captured from PCLI lvf
233: commandLine.clearArgs();
234: commandLine.setExecutable(getExecutable(GET_EXE));
235:
236: if (getConfig() != null && getConfig().length() > 0) {
237: commandLine.createArgument().setValue(
238: "-c" + getConfig());
239: }
240:
241: if (getForce() != null && getForce().equals("yes")) {
242: commandLine.createArgument().setValue("-Y");
243: } else {
244: commandLine.createArgument().setValue("-N");
245: }
246:
247: if (getPromotiongroup() != null) {
248: commandLine.createArgument().setValue(
249: "-G" + getPromotiongroup());
250: } else {
251: if (getLabel() != null) {
252: commandLine.createArgument().setValue(
253: "-v" + getLabel());
254: } else {
255: if (getRevision() != null) {
256: commandLine.createArgument().setValue(
257: "-r" + getRevision());
258: }
259: }
260: }
261:
262: if (updateOnly) {
263: commandLine.createArgument().setValue("-U");
264: }
265:
266: commandLine.createArgument().setValue(
267: "@" + tmp2.getAbsolutePath());
268: log("Getting files", Project.MSG_INFO);
269: log("Executing " + commandLine.toString(),
270: Project.MSG_VERBOSE);
271: result = runCmd(commandLine, new LogStreamHandler(this ,
272: Project.MSG_INFO, Project.MSG_WARN));
273: if (result != 0 && !ignorerc) {
274: String msg = "Failed executing: "
275: + commandLine.toString() + ". Return code was "
276: + result;
277: throw new BuildException(msg, getLocation());
278: }
279:
280: } catch (FileNotFoundException e) {
281: String msg = "Failed executing: " + commandLine.toString()
282: + ". Exception: " + e.getMessage();
283: throw new BuildException(msg, getLocation());
284: } catch (IOException e) {
285: String msg = "Failed executing: " + commandLine.toString()
286: + ". Exception: " + e.getMessage();
287: throw new BuildException(msg, getLocation());
288: } catch (ParseException e) {
289: String msg = "Failed executing: " + commandLine.toString()
290: + ". Exception: " + e.getMessage();
291: throw new BuildException(msg, getLocation());
292: } finally {
293: if (tmp != null) {
294: tmp.delete();
295: }
296: if (tmp2 != null) {
297: tmp2.delete();
298: }
299: }
300: }
301:
302: /**
303: * Parses the file and creates the folders specified in the output section
304: */
305: private void createFolders(File file) throws IOException,
306: ParseException {
307: BufferedReader in = null;
308: try {
309: in = new BufferedReader(new FileReader(file));
310: MessageFormat mf = new MessageFormat(getFilenameFormat());
311: String line = in.readLine();
312: while (line != null) {
313: log("Considering \"" + line + "\"", Project.MSG_VERBOSE);
314: if (line.startsWith("\"\\") // Checking for "\
315: || line.startsWith("\"/") // or "/
316: // or "X:\...
317: || (line.length() > POS_3
318: && line.startsWith("\"")
319: && Character.isLetter(line
320: .charAt(POS_1))
321: && String.valueOf(line.charAt(POS_2))
322: .equals(":") && String.valueOf(
323: line.charAt(POS_3)).equals("\\"))) {
324: Object[] objs = mf.parse(line);
325: String f = (String) objs[1];
326: // Extract the name of the directory from the filename
327: int index = f.lastIndexOf(File.separator);
328: if (index > -1) {
329: File dir = new File(f.substring(0, index));
330: if (!dir.exists()) {
331: log("Creating " + dir.getAbsolutePath(),
332: Project.MSG_VERBOSE);
333: if (dir.mkdirs()) {
334: log("Created " + dir.getAbsolutePath(),
335: Project.MSG_INFO);
336: } else {
337: log("Failed to create "
338: + dir.getAbsolutePath(),
339: Project.MSG_INFO);
340: }
341: } else {
342: log(dir.getAbsolutePath()
343: + " exists. Skipping",
344: Project.MSG_VERBOSE);
345: }
346: } else {
347: log("File separator problem with " + line,
348: Project.MSG_WARN);
349: }
350: } else {
351: log("Skipped \"" + line + "\"", Project.MSG_VERBOSE);
352: }
353: line = in.readLine();
354: }
355: } finally {
356: if (in != null) {
357: in.close();
358: }
359: }
360: }
361:
362: /**
363: * Simple hack to handle the PVCS command-line tools botch when
364: * handling UNC notation.
365: * @throws IOException if there is an error.
366: */
367: private void massagePCLI(File in, File out) throws IOException {
368: BufferedReader inReader = null;
369: BufferedWriter outWriter = null;
370: try {
371: inReader = new BufferedReader(new FileReader(in));
372: outWriter = new BufferedWriter(new FileWriter(out));
373: String s = null;
374: while ((s = inReader.readLine()) != null) {
375: String sNormal = s.replace('\\', '/');
376: outWriter.write(sNormal);
377: outWriter.newLine();
378: }
379: } finally {
380: if (inReader != null) {
381: inReader.close();
382: }
383: if (outWriter != null) {
384: outWriter.close();
385: }
386: }
387: }
388:
389: /**
390: * Get network name of the PVCS repository
391: * @return String
392: */
393: public String getRepository() {
394: return repository;
395: }
396:
397: /**
398: * The filenameFormat attribute defines a MessageFormat string used
399: * to parse the output of the pcli command. It defaults to
400: * <code>{0}-arc({1})</code>. Repositories where the archive
401: * extension is not -arc should set this.
402: * @return the filename format attribute.
403: */
404: public String getFilenameFormat() {
405: return filenameFormat;
406: }
407:
408: /**
409: * The format of the folder names; optional.
410: * This must be in a format suitable for
411: * <code>java.text.MessageFormat</code>.
412: * Index 1 of the format will be used as the file name.
413: * Defaults to <code>{0}-arc({1})</code>
414: * @param f the format to use.
415: */
416: public void setFilenameFormat(String f) {
417: filenameFormat = f;
418: }
419:
420: /**
421:
422: * The lineStart attribute is used to parse the output of the pcli
423: * command. It defaults to <code>"P:</code>. The parser already
424: * knows about / and \\, this property is useful in cases where the
425: * repository is accessed on a Windows platform via a drive letter
426: * mapping.
427: * @return the lineStart attribute.
428: */
429: public String getLineStart() {
430: return lineStart;
431: }
432:
433: /**
434: * What a valid return value from PVCS looks like
435: * when it describes a file. Defaults to <code>"P:</code>.
436: * If you are not using an UNC name for your repository and the
437: * drive letter <code>P</code> is incorrect for your setup, you may
438: * need to change this value, UNC names will always be
439: * accepted.
440: * @param l the value to use.
441: */
442: public void setLineStart(String l) {
443: lineStart = l;
444: }
445:
446: /**
447: * The network name of the PVCS repository; required.
448: * @param repo String
449: */
450: public void setRepository(String repo) {
451: repository = repo;
452: }
453:
454: /**
455: * Get name of the project in the PVCS repository
456: * @return String
457: */
458: public String getPvcsproject() {
459: return pvcsProject;
460: }
461:
462: /**
463: * The project within the PVCS repository to extract files from;
464: * optional, default "/"
465: * @param prj String
466: */
467: public void setPvcsproject(String prj) {
468: pvcsProject = prj;
469: }
470:
471: /**
472: * Get name of the project in the PVCS repository
473: * @return Vector
474: */
475: public Vector getPvcsprojects() {
476: return pvcsProjects;
477: }
478:
479: /**
480: * Get name of the workspace to store the retrieved files
481: * @return String
482: */
483: public String getWorkspace() {
484: return workspace;
485: }
486:
487: /**
488: * Workspace to use; optional.
489: * By specifying a workspace, the files are extracted to that location.
490: * A PVCS workspace is a name for a location of the workfiles and
491: * isn't as such the location itself.
492: * You define the location for a workspace using the PVCS GUI clients.
493: * If this isn't specified the default workspace for the current user is used.
494: * @param ws String
495: */
496: public void setWorkspace(String ws) {
497: workspace = ws;
498: }
499:
500: /**
501: * Get name of the PVCS bin directory
502: * @return String
503: */
504: public String getPvcsbin() {
505: return pvcsbin;
506: }
507:
508: /**
509: * Specifies the location of the PVCS bin directory; optional if on the PATH.
510: * On some systems the PVCS executables <i>pcli</i>
511: * and <i>get</i> are not found in the PATH. In such cases this attribute
512: * should be set to the bin directory of the PVCS installation containing
513: * the executables mentioned before. If this attribute isn't specified the
514: * tag expects the executables to be found using the PATH environment variable.
515: * @param bin PVCS bin directory
516: * @todo use a File setter and resolve paths.
517: */
518: public void setPvcsbin(String bin) {
519: pvcsbin = bin;
520: }
521:
522: /**
523: * Get value of force
524: * @return String
525: */
526: public String getForce() {
527: return force;
528: }
529:
530: /**
531: * Specifies the value of the force argument; optional.
532: * If set to <i>yes</i> all files that exists and are
533: * writable are overwritten. Default <i>no</i> causes the files
534: * that are writable to be ignored. This stops the PVCS command
535: * <i>get</i> to stop asking questions!
536: * @todo make a boolean setter
537: * @param f String (yes/no)
538: */
539: public void setForce(String f) {
540: if (f != null && f.equalsIgnoreCase("yes")) {
541: force = "yes";
542: } else {
543: force = "no";
544: }
545: }
546:
547: /**
548: * Get value of promotiongroup
549: * @return String
550: */
551: public String getPromotiongroup() {
552: return promotiongroup;
553: }
554:
555: /**
556: * Specifies the name of the promotiongroup argument
557: * @param w String
558: */
559: public void setPromotiongroup(String w) {
560: promotiongroup = w;
561: }
562:
563: /**
564: * Get value of label
565: * @return String
566: */
567: public String getLabel() {
568: return label;
569: }
570:
571: /**
572: * Only files marked with this label are extracted; optional.
573: * @param l String
574: */
575: public void setLabel(String l) {
576: label = l;
577: }
578:
579: /**
580: * Get value of revision
581: * @return String
582: */
583: public String getRevision() {
584: return revision;
585: }
586:
587: /**
588: * Only files with this revision are extract; optional.
589: * @param r String
590: */
591: public void setRevision(String r) {
592: revision = r;
593: }
594:
595: /**
596: * Get value of ignorereturncode
597: * @return String
598: */
599: public boolean getIgnoreReturnCode() {
600: return ignorerc;
601: }
602:
603: /**
604: * If set to true the return value from executing the pvcs
605: * commands are ignored; optional, default false.
606: * @param b a <code>boolean</code> value.
607: */
608: public void setIgnoreReturnCode(boolean b) {
609: ignorerc = b;
610: }
611:
612: /**
613: * Specify a project within the PVCS repository to extract files from.
614: * @param p the pvcs project to use.
615: */
616: public void addPvcsproject(PvcsProject p) {
617: pvcsProjects.addElement(p);
618: }
619:
620: /**
621: * get the updateOnly attribute.
622: * @return the updateOnly attribute.
623: */
624: public boolean getUpdateOnly() {
625: return updateOnly;
626: }
627:
628: /**
629: * If set to <i>true</i> files are fetched only if
630: * newer than existing local files; optional, default false.
631: * @param l a <code>boolean</code> value.
632: */
633: public void setUpdateOnly(boolean l) {
634: updateOnly = l;
635: }
636:
637: /**
638: * returns the path of the configuration file to be used
639: * @return the path of the config file
640: */
641: public String getConfig() {
642: return config;
643: }
644:
645: /**
646: * Sets a configuration file other than the default to be used.
647: * These files have a .cfg extension and are often found in archive or pvcsprop folders.
648: * @param f config file - can be given absolute or relative to ant basedir
649: */
650: public void setConfig(File f) {
651: config = f.toString();
652: }
653:
654: /**
655: * Get the userid.
656: * @return the userid.
657: */
658: public String getUserId() {
659: return userId;
660: }
661:
662: /**
663: * User ID
664: * @param u the value to use.
665: */
666: public void setUserId(String u) {
667: userId = u;
668: }
669:
670: /**
671: * Creates a Pvcs object
672: */
673: public Pvcs() {
674: super ();
675: pvcsProject = null;
676: pvcsProjects = new Vector();
677: workspace = null;
678: repository = null;
679: pvcsbin = null;
680: force = null;
681: promotiongroup = null;
682: label = null;
683: ignorerc = false;
684: updateOnly = false;
685: lineStart = "\"P:";
686: filenameFormat = "{0}-arc({1})";
687: }
688: }
|