// 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] /// /// /// /// /// 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 } }