// NAnt - A .NET build tool
// Copyright (C) 2001-2003 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Clayton Harbour (claytonharbour@sporadicism.com)
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Globalization;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Tasks;
using NAnt.Core.Types;
using NAnt.Core.Util;
using NAnt.SourceControl.Types;
using ICSharpCode.SharpCvsLib.FileSystem;
using ICSharpCode.SharpCvsLib.Exceptions;
namespace NAnt.SourceControl.Tasks {
///
/// A base class for creating tasks for executing CVS client commands on a
/// CVS repository.
///
public abstract class AbstractCvsTask : AbstractSourceControlTask {
#region Private Static Fields
private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endregion Private Static Fields
#region Protected Static Fields
///
/// Default value for the recursive directive. The default is
/// .
///
protected const bool DefaultRecursive = false;
///
/// Default value for the quiet command.
///
protected const bool DefaultQuiet = false;
///
/// Default value for the really quiet command.
///
protected const bool DefaultReallyQuiet = false;
///
/// An environment variable that holds path information about where
/// cvs is located.
///
protected const string CvsHome = "CVS_HOME";
///
/// Name of the password file that cvs stores pserver
/// cvsroot/ password pairings.
///
protected const String CvsPassfile = ".cvspass";
///
/// The default compression level to use for cvs commands.
///
protected const int DefaultCompressionLevel = 3;
///
/// The default use of binaries, defaults to use sharpcvs.
///
protected const bool DefaultUseSharpCvsLib = true;
///
/// The name of the cvs executable.
///
protected const string CvsExe = "cvs.exe";
///
/// The temporary name of the sharpcvslib binary file, to avoid
/// conflicts in the path variable.
///
protected const string SharpCvsExe = "scvs.exe";
///
/// Environment variable that holds the executable name that is used for
/// ssh communication.
///
protected const string CvsRsh = "CVS_RSH";
///
/// Property name used to specify on a project level whether sharpcvs is
/// used or not.
///
protected const string UseSharpCvsLibProp = "sourcecontrol.usesharpcvslib";
#endregion "Protected Static Fields
#region Private Instance Fields
private string _module;
private bool _useSharpCvsLib = DefaultUseSharpCvsLib;
private bool _isUseSharpCvsLibSet = false;
private FileInfo _cvsFullPath;
private string _sharpcvslibExeName;
private CvsFileSet _cvsFileSet = new CvsFileSet();
#endregion Private Instance Fields
#region Protected Instance Contructors
///
/// Initializes a new instance of the
/// class.
///
protected AbstractCvsTask () : base() {
_sharpcvslibExeName =
Path.Combine (System.AppDomain.CurrentDomain.BaseDirectory, SharpCvsExe);
}
#endregion Protected Instance Constructors
#region Protected Instance Properties
///
/// The environment name for the ssh variable.
///
protected override string SshEnv {
get {return CvsRsh;}
}
///
/// The name of the cvs binary, or cvs.exe at the time this
/// was written.
///
protected override string VcsExeName {
get {return CvsExe;}
}
///
/// The name of the pass file, or .cvspass at the time
/// of this writing.
///
protected override string PassFileName {
get {return CvsPassfile;}
}
///
/// The name of the version control system specific home environment
/// variable.
///
protected override string VcsHomeEnv {
get {return CvsHome;}
}
///
/// Specify if the module is needed for this cvs command. It is
/// only needed if there is no module information on the local file
/// system.
///
protected virtual bool IsModuleNeeded {
get { return true; }
}
#endregion
#region Public Instance Properties
///
/// Used to specify the version control system (VCS) files that are going
/// to be acted on.
///
[BuildElement("fileset")]
public CvsFileSet CvsFileSet {
get { return this._cvsFileSet; }
set { this._cvsFileSet = value; }
}
///
/// Get the cvs file set.
///
public override FileSet VcsFileSet{
get { return this.CvsFileSet; }
}
///
/// The name of the cvs executable.
///
public override string ExeName {
get {
if (null != CvsFullPath) {
return CvsFullPath.FullName;
}
string _exeNameTemp;
if (UseSharpCvsLib) {
_exeNameTemp = _sharpcvslibExeName;
} else {
FileInfo vcsFile = DeriveVcsFromEnvironment();
if (vcsFile == null) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"'{0}' could not be found on the system.", VcsExeName),
Location);
}
_exeNameTemp = vcsFile.FullName;
}
Logger.Debug("_sharpcvslibExeName: " + _sharpcvslibExeName);
Logger.Debug("_exeNameTemp: " + _exeNameTemp);
Properties[PropExeName] = _exeNameTemp;
return _exeNameTemp;
}
}
///
/// The full path to the cvs binary used. The cvs tasks will attempt to
/// "guess" the location of your cvs binary based on your path. If the
/// task is unable to resolve the location, or resolves it incorrectly
/// this can be used to manually specify the path.
///
///
/// A full path (i.e. including file name) of your cvs binary:
/// On Windows: c:\vcs\cvs\cvs.exe
/// On *nix: /usr/bin/cvs
///
[TaskAttribute("cvsfullpath", Required=false)]
public FileInfo CvsFullPath {
get {return _cvsFullPath;}
set {_cvsFullPath = value;}
}
///
///
/// The cvs root variable has the following components:
///
///
/// [protocol]:[username]@[servername]:[server path]
///
/// - protocol: ext, pserver, ssh (sharpcvslib); if you are not using sharpcvslib consult your cvs documentation.
/// - username: [username]
/// - servername: cvs.sourceforge.net
/// - server path: /cvsroot/nant
///
///
///
///
/// NAnt anonymous cvsroot:
///
/// :pserver:anonymous@cvs.sourceforge.net:/cvsroot/nant
///
///
[TaskAttribute("cvsroot", Required=false)]
[StringValidator(AllowEmpty=false)]
public override string Root {
get {
if (null == base.Root) {
try {
ICSharpCode.SharpCvsLib.FileSystem.Root root =
ICSharpCode.SharpCvsLib.FileSystem.Root.Load(this.DestinationDirectory);
this.Root = root.FileContents;
} catch (ICSharpCode.SharpCvsLib.Exceptions.CvsFileNotFoundException) {
throw new BuildException (string.Format("Cvs/Root file not found in {0}, please perform a checkout.",
this.DestinationDirectory.FullName));
}
}
return base.Root;
}
set { base.Root = StringUtils.ConvertEmptyToNull(value); }
}
///
/// The module to perform an operation on.
///
///
/// The module to perform an operation on. This is a normal file/folder
/// name without path information.
///
///
/// In NAnt the module name would be:
/// nant
///
[TaskAttribute("module", Required=false)]
[StringValidator(AllowEmpty=true)]
public virtual string Module {
get {
if (null == _module) {
try {
Repository repository = Repository.Load(this.DestinationDirectory);
this._module = repository.ModuleName;
} catch (ICSharpCode.SharpCvsLib.Exceptions.CvsFileNotFoundException) {
throw new BuildException (string.Format("Cvs/Repository file not found in {0}, please perform a checkout.",
this.DestinationDirectory.FullName));
}
}
return _module;
}
set { _module = StringUtils.ConvertEmptyToNull(value); }
}
///
///
/// if the SharpCvsLib binaries that come bundled
/// with NAnt should be used to perform the cvs commands,
/// otherwise.
///
///
/// You may also specify an override value for all cvs tasks instead
/// of specifying a value for each. To do this set the property
/// sourcecontrol.usesharpcvslib to .
///
///
/// If you choose not to use SharpCvsLib to checkout from cvs you will
/// need to include a cvs.exe binary in your path.
///
///
///
/// To use a cvs client in your path instead of sharpcvslib specify
/// the property:
/// >property name="sourcecontrol.usesharpcvslib" value="false"<
///
/// The default settings is to use sharpcvslib and the setting closest
/// to the task execution is used to determine which value is used
/// to execute the process.
///
/// For instance if the attribute usesharpcvslib was set to false
/// and the global property was set to true, the usesharpcvslib is
/// closes to the point of execution and would be used and is false.
/// Therefore the sharpcvslib binary would NOT be used.
///
[TaskAttribute("usesharpcvslib", Required=false)]
public virtual bool UseSharpCvsLib {
get {return _useSharpCvsLib;}
set {
_isUseSharpCvsLibSet = true;
_useSharpCvsLib = value;
}
}
///
/// The executable to use for ssh communication.
///
[TaskAttribute("cvsrsh", Required=false)]
public override FileInfo Ssh {
get {return base.Ssh;}
set {base.Ssh = value;}
}
///
/// Indicates if the output from the cvs command should be supressed.
/// The default is .
///
[TaskAttribute("quiet", Required=false)]
[BooleanValidator()]
public bool Quiet {
get {
Option option = (Option)GlobalOptions["quiet"];
return null == option ? false : option.IfDefined;
}
set {SetGlobalOption("quiet", "-q", value);}
}
///
/// Indicates if the output from the cvs command should be stopped.
/// The default is .
///
[TaskAttribute("reallyquiet", Required=false)]
[BooleanValidator()]
public bool ReallyQuiet {
get {
Option option = (Option)GlobalOptions["reallyquiet"];
return null == option ? false : option.IfDefined;
}
set {SetGlobalOption("reallyquiet", "-Q", value);}
}
///
/// if the sandbox files should be checked out in
/// read only mode. The default is .
///
[TaskAttribute("readonly", Required=false)]
[BooleanValidator()]
public bool ReadOnly {
get {
Option option = (Option)GlobalOptions["readonly"];
return null == option ? false : option.IfDefined;
}
set {SetGlobalOption("readonly", "-r", value);}
}
///
/// if the sandbox files should be checked out in
/// read/write mode. The default is .
///
[TaskAttribute("readwrite", Required=false)]
[BooleanValidator()]
public bool ReadWrite {
get {
Option option = (Option)GlobalOptions["readwrite"];
return null == option ? false : option.IfDefined;
}
set {SetGlobalOption("readwrite", "-w", value);}
}
///
/// Compression level to use for all net traffic. This should be a value from 1-9.
///
///
/// NOTE: This is not available on sharpcvslib.
///
[TaskAttribute("compressionlevel")]
public int CompressionLevel {
get {
Option option = (Option)GlobalOptions["compressionlevel"];
return null == option ? DefaultCompressionLevel : Convert.ToInt32(option.Value);
}
set {SetGlobalOption("readwrite", String.Format("-z{0}", value), true);}
}
#endregion Public Instance Properties
#region Override Task Implementation
///
/// Build up the command line arguments, determine which executable is being
/// used and find the path to that executable and set the working
/// directory.
///
/// The process to prepare.
protected override void PrepareProcess (Process process) {
// Although a global property can be set, take the property closest
// to the task execution, which is the attribute on the task itself.
if (!_isUseSharpCvsLibSet &&
(null == Properties || null == Properties[UseSharpCvsLibProp])) {
// if not set and the global property is null then use the default
_useSharpCvsLib = UseSharpCvsLib;
} else if (!_isUseSharpCvsLibSet &&
null != Properties[UseSharpCvsLibProp]){
try {
_useSharpCvsLib =
System.Convert.ToBoolean(Properties[UseSharpCvsLibProp]);
} catch (Exception) {
throw new BuildException (UseSharpCvsLib + " must be convertable to a boolean.");
}
}
Logger.Debug("number of arguments: " + Arguments.Count);
// if set, pass cvsroot to command line tool
if (Root != null) {
Arguments.Add(new Argument(string.Format(CultureInfo.InvariantCulture,
"-d{0}", Root)));
}
if (this.UseSharpCvsLib) {
UseRuntimeEngine = true;
}
// Set verbose logging on the #cvslib client if used.
if (this.UseSharpCvsLib && this.Verbose) {
SetGlobalOption("verbose", String.Format("-verbose"), true);
}
AppendGlobalOptions();
Arguments.Add(new Argument(CommandName));
AppendCommandOptions();
Log(Level.Debug, "Commandline args are null: {0}",
((null == CommandLineArguments) ? "yes" : "no"));
Log(Level.Debug, "Commandline: {0}", CommandLineArguments);
if (null != CommandLineArguments) {
Arguments.Add(new Argument(CommandLineArguments));
}
AppendSubCommandArgs();
AppendFiles();
if (IsModuleNeeded && null == Module) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Cvs module is required for this action."),
Location);
}
if (IsModuleNeeded) {
Arguments.Add(new Argument(Module));
}
if (!Directory.Exists(DestinationDirectory.FullName)) {
Directory.CreateDirectory(DestinationDirectory.FullName);
}
base.PrepareProcess(process);
process.StartInfo.FileName = ExeName;
process.StartInfo.WorkingDirectory = DestinationDirectory.FullName;
Log(Level.Verbose, "Working directory: {0}", process.StartInfo.WorkingDirectory);
Log(Level.Verbose, "Executable: {0}", process.StartInfo.FileName);
Log(Level.Verbose, "Arguments: {0}", process.StartInfo.Arguments);
}
///
/// Override to append any commands before the modele and files.
///
protected virtual void AppendSubCommandArgs() {
}
#endregion
#region Private Instance Methods
private void AppendGlobalOptions () {
foreach (Option option in GlobalOptions.Values) {
// Log(Level.Verbose, "Type '{0}'.", optionte.GetType());
if (!option.IfDefined || option.UnlessDefined) {
// skip option
continue;
}
AddArg(option.Value);
}
}
///
/// Append the command line options or commen names for the options
/// to the generic options collection. This is then piped to the
/// command line as a switch.
///
private void AppendCommandOptions () {
foreach (Option option in CommandOptions.Values) {
if (!option.IfDefined || option.UnlessDefined) {
// skip option
continue;
}
AddArg(option.Value);
}
}
///
/// Add the given argument to the command line options. Note that are not explicitly
/// quoted are split into seperate arguments. This is to resolve a recent issue
/// with quoting command line arguments.
///
///
protected void AddArg (String arg) {
if (arg.IndexOf(" ") > -1 && arg.IndexOf("\"") == -1) {
string[] args = arg.Split(' ');
foreach (string targ in args) {
Arguments.Add(
new Argument(String.Format(CultureInfo.InvariantCulture,"{0}", targ)));
}
} else {
Arguments.Add(new Argument(String.Format(CultureInfo.InvariantCulture,"{0}",
arg)));
}
}
#endregion Private Instance Methods
}
}