blob: 93ef91b6081b7143f88ed752eea4f2549cf82fb8 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "Ant" and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.vss;
import org.apache.tools.ant.types.EnumeratedAttribute;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Execute;
import org.apache.tools.ant.taskdefs.LogStreamHandler;
import org.apache.tools.ant.types.Commandline;
/**
* A base class for creating tasks for executing commands on Visual SourceSafe.
* <p>
* The class extends the 'exec' task as it operates by executing the ss.exe program
* supplied with SourceSafe. By default the task expects ss.exe to be in the path,
* you can override this be specifying the ssdir attribute.
* </p>
* <p>
* This class provides set and get methods for 'login' and 'vsspath' attributes. It
* also contains constants for the flags that can be passed to SS.
* </p>
*
* @author Craig Cottingham
* @author Andrew Everitt
* @author Jesse Stockall
*/
public abstract class MSVSS extends Task implements MSVSSConstants {
private String m_SSDir = null;
private String m_vssLogin = null;
private String m_vssPath = null;
private String m_serverPath = null;
/** Version */
private String m_Version = null;
/** Date */
private String m_Date = null;
/** Label */
private String m_Label = null;
/** Auto response */
private String m_AutoResponse = null;
/** Local path */
private String m_LocalPath = null;
/** Comment */
private String m_Comment = null;
/** From label */
private String m_FromLabel = null;
/** To label */
private String m_ToLabel = null;
/** Output file name */
private String m_OutputFileName = null;
/** User */
private String m_User = null;
/** From date */
private String m_FromDate = null;
/** To date */
private String m_ToDate = null;
/** History style */
private String m_Style = null;
/** Quiet defaults to false */
private boolean m_Quiet = false;
/** Recursive defaults to false */
private boolean m_Recursive = false;
/** Writable defaults to false */
private boolean m_Writable = false;
/** Fail on error defaults to true */
private boolean m_FailOnError = true;
/** Get local copy for checkout defaults to true */
private boolean m_getLocalCopy = true;
/** Number of days offset for History */
private int m_NumDays = Integer.MIN_VALUE;
/** Date format for History */
private DateFormat m_DateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
/** Timestamp for retreived files */
private CurrentModUpdated m_timestamp = null;
/** Behaviour for writable files */
private WritableFiles m_writablefiles = null;
/**
* Each sub-class must implemnt this method and return the constructed
* command line to be executed. It is up to the sub-task to determine the
* required attrubutes and their order.
*
* @return The Constructed command line.
*/
abstract Commandline buildCmdLine();
/**
* Directory where <code>ss.exe</code> resides.
* By default the task expects it to be in the PATH.
*
* @param dir The directory containing ss.exe.
*/
public final void setSsdir(String dir) {
m_SSDir = Project.translatePath(dir);
}
/**
* Login to use when accessing VSS, formatted as "username,password".
* <p>
* You can omit the password if your database is not password protected.
* If you have a password and omit it, Ant will hang.
*
* @param login The login string to use.
*/
public final void setLogin(String login) {
m_vssLogin = login;
}
/**
* SourceSafe path which specifies the project/file(s) you wish to perform
* the action on.
* <p>
* A prefix of 'vss://' will be removed if specified.
*
* @param vssPath The VSS project path.
*
* @ant.attribute group="required"
*/
public final void setVsspath(String vssPath) {
String projectPath;
if (vssPath.startsWith("vss://")) {
projectPath = vssPath.substring(5);
} else {
projectPath = vssPath;
}
if (projectPath.startsWith(PROJECT_PREFIX)) {
m_vssPath = projectPath;
} else {
m_vssPath = PROJECT_PREFIX + projectPath;
}
}
/**
* Directory where <code>srssafe.ini</code> resides.
*
* @param serverPath The path to the VSS server.
*/
public final void setServerpath(String serverPath) {
m_serverPath = serverPath;
}
/**
* Indicates if the build should fail if the Sourcesafe command does. Defaults to true.
*
* @param failOnError True if task should fail on any error.
*/
public final void setFailOnError (boolean failOnError) {
m_FailOnError = failOnError;
}
/**
* Executes the task. <br>
* Builds a command line to execute ss.exe and then calls Exec's run method
* to execute the command line.
* @throws BuildException if the command cannot execute.
*/
public void execute() throws BuildException {
int result = 0;
Commandline commandLine = buildCmdLine();
result = run(commandLine);
if (Execute.isFailure(result) && getFailOnError()) {
String msg = "Failed executing: " + formatCommandLine(commandLine)
+ " With a return code of " + result;
throw new BuildException(msg, getLocation());
}
}
// Special setters for the sub-classes
protected void setInternalComment(String text) {
m_Comment = text;
}
protected void setInternalAutoResponse(String text) {
m_AutoResponse = text;
}
protected void setInternalDate(String text) {
m_Date = text;
}
protected void setInternalDateFormat(DateFormat date) {
m_DateFormat = date;
}
protected void setInternalFailOnError(boolean fail) {
m_FailOnError = fail;
}
protected void setInternalFromDate(String text) {
m_FromDate = text;
}
protected void setInternalFromLabel(String text) {
m_FromLabel = text;
}
protected void setInternalLabel(String text) {
m_Label = text;
}
protected void setInternalLocalPath(String text) {
m_LocalPath = text;
}
protected void setInternalNumDays(int days) {
m_NumDays = days;
}
protected void setInternalOutputFilename(String text) {
m_OutputFileName = text;
}
protected void setInternalQuiet(boolean quiet) {
m_Quiet = quiet;
}
protected void setInternalRecursive(boolean recursive) {
m_Recursive = recursive;
}
protected void setInternalStyle(String style) {
m_Style = style;
}
protected void setInternalToDate(String text) {
m_ToDate = text;
}
protected void setInternalToLabel(String text) {
m_ToLabel = text;
}
protected void setInternalUser(String user) {
m_User = user;
}
protected void setInternalVersion(String text) {
m_Version = text;
}
protected void setInternalWritable(boolean writable) {
m_Writable = writable;
}
protected void setInternalFileTimeStamp(CurrentModUpdated timestamp) {
m_timestamp = timestamp;
}
protected void setInternalWritableFiles(WritableFiles files) {
m_writablefiles = files;
}
protected void setInternalGetLocalCopy(boolean get) {
m_getLocalCopy = get;
}
/**
* Gets the sscommand string. "ss" or "c:\path\to\ss"
*
* @return The path to ss.exe or just ss if sscommand is not set.
*/
protected String getSSCommand() {
if (m_SSDir == null) {
return SS_EXE;
}
return m_SSDir.endsWith(File.separator) ? m_SSDir + SS_EXE : m_SSDir
+ File.separator + SS_EXE;
}
/**
* Gets the vssserverpath string.
*
* @return null if vssserverpath is not set.
*/
protected String getVsspath() {
return m_vssPath;
}
/**
* Gets the quiet string. -O-
*
* @return An empty string if quiet is not set or is false.
*/
protected String getQuiet() {
return m_Quiet ? FLAG_QUIET : "";
}
/**
* Gets the recursive string. "-R"
*
* @return An empty string if recursive is not set or is false.
*/
protected String getRecursive() {
return m_Recursive ? FLAG_RECURSION : "";
}
/**
* Gets the writable string. "-W"
*
* @return An empty string if writable is not set or is false.
*/
protected String getWritable() {
return m_Writable ? FLAG_WRITABLE : "";
}
/**
* Gets the label string. "-Lbuild1"
*
* Max label length is 32 chars
*
* @return An empty string if label is not set.
*/
protected String getLabel() {
if (m_Label != null && m_Label.length() > 0) {
return FLAG_LABEL + getShortLabel();
} else {
return "";
}
}
/**
* return at most the 30 first chars of the label, logging a warning message about the truncation
* @return at most the 30 first chars of the label
*/
private String getShortLabel() {
if (m_Label != null && m_Label.length() > 31) {
String label = m_Label.substring(0, 30);
log("Label is longer than 31 characters, truncated to: " + label, Project.MSG_WARN);
return label;
} else {
return m_Label;
}
}
/**
* Gets the style string. "-Lbuild1"
*
* @return An empty string if label is not set.
*/
protected String getStyle() {
return m_Style != null ? m_Style : "";
}
/**
* Gets the version string. Returns the first specified of version "-V1.0",
* date "-Vd01.01.01", label "-Vlbuild1".
*
* @return An empty string if a version, date and label are not set.
*/
protected String getVersionDateLabel() {
if (m_Version != null) {
return FLAG_VERSION + m_Version;
} else if (m_Date != null) {
return FLAG_VERSION_DATE + m_Date;
} else {
// Use getShortLabel() so labels longer then 30 char are truncated
// and the user is warned
String label = getShortLabel();
if (label != null && !label.equals("")) {
return FLAG_VERSION_LABEL + label;
}
}
return "";
}
/**
* Gets the version string.
*
* @return An empty string if a version is not set.
*/
protected String getVersion() {
return m_Version != null ? FLAG_VERSION + m_Version : "";
}
/**
* Gets the localpath string. "-GLc:\source" <p>
*
* The localpath is created if it didn't exist.
*
* @return An empty string if localpath is not set.
*/
protected String getLocalpath() {
if (m_LocalPath == null) {
return "";
} else {
// make sure m_LocalDir exists, create it if it doesn't
File dir = getProject().resolveFile(m_LocalPath);
if (!dir.exists()) {
boolean done = dir.mkdirs();
if (!done) {
String msg = "Directory " + m_LocalPath + " creation was not "
+ "successful for an unknown reason";
throw new BuildException(msg, getLocation());
}
getProject().log("Created dir: " + dir.getAbsolutePath());
}
return FLAG_OVERRIDE_WORKING_DIR + m_LocalPath;
}
}
/**
* Gets the comment string. "-Ccomment text"
*
* @return A comment of "-" if comment is not set.
*/
protected String getComment() {
return m_Comment != null ? FLAG_COMMENT + m_Comment : FLAG_COMMENT + "-";
}
/**
* Gets the auto response string. This can be Y "-I-Y" or N "-I-N".
*
* @return The default value "-I-" if autoresponse is not set.
*/
protected String getAutoresponse() {
if (m_AutoResponse == null) {
return FLAG_AUTORESPONSE_DEF;
} else if (m_AutoResponse.equalsIgnoreCase("Y")) {
return FLAG_AUTORESPONSE_YES;
} else if (m_AutoResponse.equalsIgnoreCase("N")) {
return FLAG_AUTORESPONSE_NO;
} else {
return FLAG_AUTORESPONSE_DEF;
}
}
/**
* Gets the login string. This can be user and password, "-Yuser,password"
* or just user "-Yuser".
*
* @return An empty string if login is not set.
*/
protected String getLogin() {
return m_vssLogin != null ? FLAG_LOGIN + m_vssLogin : "";
}
/**
* Gets the output file string. "-Ooutput.file"
*
* @return An empty string if user is not set.
*/
protected String getOutput() {
return m_OutputFileName != null ? FLAG_OUTPUT + m_OutputFileName : "";
}
/**
* Gets the user string. "-Uusername"
*
* @return An empty string if user is not set.
*/
protected String getUser() {
return m_User != null ? FLAG_USER + m_User : "";
}
/**
* Gets the version string. This can be to-from "-VLbuild2~Lbuild1", from
* "~Lbuild1" or to "-VLbuild2".
*
* @return An empty string if neither tolabel or fromlabel are set.
*/
protected String getVersionLabel() {
if (m_FromLabel == null && m_ToLabel == null) {
return "";
}
if (m_FromLabel != null && m_ToLabel != null) {
if (m_FromLabel.length() > 31) {
m_FromLabel = m_FromLabel.substring(0, 30);
log("FromLabel is longer than 31 characters, truncated to: "
+ m_FromLabel, Project.MSG_WARN);
}
if (m_ToLabel.length() > 31) {
m_ToLabel = m_ToLabel.substring(0, 30);
log("ToLabel is longer than 31 characters, truncated to: "
+ m_ToLabel, Project.MSG_WARN);
}
return FLAG_VERSION_LABEL + m_ToLabel + VALUE_FROMLABEL + m_FromLabel;
} else if (m_FromLabel != null) {
if (m_FromLabel.length() > 31) {
m_FromLabel = m_FromLabel.substring(0, 30);
log("FromLabel is longer than 31 characters, truncated to: "
+ m_FromLabel, Project.MSG_WARN);
}
return FLAG_VERSION + VALUE_FROMLABEL + m_FromLabel;
} else {
if (m_ToLabel.length() > 31) {
m_ToLabel = m_ToLabel.substring(0, 30);
log("ToLabel is longer than 31 characters, truncated to: "
+ m_ToLabel, Project.MSG_WARN);
}
return FLAG_VERSION_LABEL + m_ToLabel;
}
}
/**
* Gets the Version date string.
* @return An empty string if neither Todate or from date are set.
* @throws BuildException
*/
protected String getVersionDate() throws BuildException {
if (m_FromDate == null && m_ToDate == null
&& m_NumDays == Integer.MIN_VALUE) {
return "";
}
if (m_FromDate != null && m_ToDate != null) {
return FLAG_VERSION_DATE + m_ToDate + VALUE_FROMDATE + m_FromDate;
} else if (m_ToDate != null && m_NumDays != Integer.MIN_VALUE) {
try {
return FLAG_VERSION_DATE + m_ToDate + VALUE_FROMDATE
+ calcDate(m_ToDate, m_NumDays);
} catch (ParseException ex) {
String msg = "Error parsing date: " + m_ToDate;
throw new BuildException(msg, getLocation());
}
} else if (m_FromDate != null && m_NumDays != Integer.MIN_VALUE) {
try {
return FLAG_VERSION_DATE + calcDate(m_FromDate, m_NumDays)
+ VALUE_FROMDATE + m_FromDate;
} catch (ParseException ex) {
String msg = "Error parsing date: " + m_FromDate;
throw new BuildException(msg, getLocation());
}
} else {
return m_FromDate != null ? FLAG_VERSION + VALUE_FROMDATE
+ m_FromDate : FLAG_VERSION_DATE + m_ToDate;
}
}
/**
* Builds and returns the -G- flag if required.
*
* @return An empty string if get local copy is true.
*/
protected String getGetLocalCopy() {
return (!m_getLocalCopy) ? FLAG_NO_GET : "";
}
/**
* Gets the value of the fail on error flag.
*
* @return True if the FailOnError flag has been set or if 'writablefiles=skip'.
*/
private boolean getFailOnError() {
return getWritableFiles().equals(WRITABLE_SKIP) ? false : m_FailOnError;
}
/**
* Gets the value set for the FileTimeStamp.
* if it equals "current" then we return -GTC
* if it equals "modified" then we return -GTM
* if it equals "updated" then we return -GTU
* otherwise we return -GTC
*
* @return The default file time flag, if not set.
*/
public String getFileTimeStamp() {
if (m_timestamp == null) {
return "";
} else if (m_timestamp.getValue().equals(TIME_MODIFIED)) {
return FLAG_FILETIME_MODIFIED;
} else if (m_timestamp.getValue().equals(TIME_UPDATED)) {
return FLAG_FILETIME_UPDATED;
} else {
return FLAG_FILETIME_DEF;
}
}
/**
* Gets the value to determine the behaviour when encountering writable files.
* @return An empty String, if not set.
*/
public String getWritableFiles() {
if (m_writablefiles == null) {
return "";
} else if (m_writablefiles.getValue().equals(WRITABLE_REPLACE)) {
return FLAG_REPLACE_WRITABLE;
} else if (m_writablefiles.getValue().equals(WRITABLE_SKIP)) {
// ss.exe exits with '100', when files have been skipped
// so we have to ignore the failure
m_FailOnError = false;
return FLAG_SKIP_WRITABLE;
} else {
return "";
}
}
/**
* Sets up the required environment and executes the command line.
*
* @param cmd The command line to execute.
* @return The return code from the exec'd process.
*/
private int run(Commandline cmd) {
try {
Execute exe = new Execute(new LogStreamHandler(this,
Project.MSG_INFO,
Project.MSG_WARN));
// If location of ss.ini is specified we need to set the
// environment-variable SSDIR to this value
if (m_serverPath != null) {
String[] env = exe.getEnvironment();
if (env == null) {
env = new String[0];
}
String[] newEnv = new String[env.length + 1];
for (int i = 0; i < env.length; i++) {
newEnv[i] = env[i];
}
newEnv[env.length] = "SSDIR=" + m_serverPath;
exe.setEnvironment(newEnv);
}
exe.setAntRun(getProject());
exe.setWorkingDirectory(getProject().getBaseDir());
exe.setCommandline(cmd.getCommandline());
// Use the OS launcher so we get environment variables
exe.setVMLauncher(false);
return exe.execute();
} catch (IOException e) {
throw new BuildException(e, getLocation());
}
}
/**
* Calculates the start date for version comparison.
* <p>
* Calculates the date numDay days earlier than startdate.
* @param fromDate The start date.
* @param numDays The number of days to add.
* @return The calculated date.
* @throws ParseException
*/
private String calcDate(String fromDate, int numDays) throws ParseException {
String toDate = null;
Date currdate = new Date();
Calendar calend = new GregorianCalendar();
currdate = m_DateFormat.parse(fromDate);
calend.setTime(currdate);
calend.add(Calendar.DATE, numDays);
toDate = m_DateFormat.format(calend.getTime());
return toDate;
}
/**
* Changes the password to '***' so it isn't displayed on screen if the build fails
*
* @param cmd The command line to clean
* @return The command line as a string with out the password
*/
private String formatCommandLine(Commandline cmd) {
StringBuffer sBuff = new StringBuffer(cmd.toString());
int indexUser = sBuff.substring(0).indexOf(FLAG_LOGIN);
if (indexUser > 0) {
int indexPass = sBuff.substring(0).indexOf(",", indexUser);
int indexAfterPass = sBuff.substring(0).indexOf(" ", indexPass);
for (int i = indexPass + 1; i < indexAfterPass; i++) {
sBuff.setCharAt(i, '*');
}
}
return sBuff.toString();
}
/**
* Extention of EnumeratedAttribute to hold the values for file time stamp.
*/
public static class CurrentModUpdated extends EnumeratedAttribute {
/**
* Gets the list of allowable values.
* @return The values.
*/
public String[] getValues() {
return new String[] {TIME_CURRENT, TIME_MODIFIED, TIME_UPDATED};
}
}
/**
* Extention of EnumeratedAttribute to hold the values for writable filess.
*/
public static class WritableFiles extends EnumeratedAttribute {
/**
* Gets the list of allowable values.
* @return The values.
*/
public String[] getValues() {
return new String[] {WRITABLE_REPLACE, WRITABLE_SKIP, WRITABLE_FAIL};
}
}
}