using System; using System.Collections.Specialized; using System.IO; using System.Text.RegularExpressions; using Exortech.NetReflector; using ThoughtWorks.CruiseControl.Core.Sourcecontrol; using ThoughtWorks.CruiseControl.Core.Util; namespace ThoughtWorks.CruiseControl.Core.label { /// /// Labeller for (AccuRev Inc.'s eponymous source code control product. /// /// /// This code is based on code\sourcecontrol\AccuRev.cs and re-uses code\sourcecontrol\AccuRevHistoryParser.cs. /// [ReflectorType("accurevLabeller")] public class AccuRevLabeller : ILabeller { #region Properties /// /// Name of the AccuRev CLI command. /// /// /// Optional, defaults to "accurev.exe". /// [ReflectorProperty("executable", Required = false)] public string Executable = "accurev.exe"; /// /// The location of the AccuRev home directory, either absolute or relative to the project artifact /// directory. If not specified, AccuRev will follow its rules for determining the location. The /// home directory itself is always called ".accurev", and AccuRev will create it if there isn't /// already one present in the home directory. /// /// /// Optional, default is to let AccuRev decide where the home directory is. /// [ReflectorProperty("homeDir", Required = false)] public string AccuRevHomeDir = null; /// /// If true, log in to AccuRev using the specified principal and password. /// /// /// Optional, default is not to log in. If set to true, "principal" and "password" must also be set. /// [ReflectorProperty("login", Required = false)] public bool LogIn = false; /// /// The password for the AccuRev "principal" (userid). /// /// /// Optional, default is no password. Only necessary if "login" is set to "true". /// [ReflectorProperty("password", Required = false)] public string AccuRevPassword = null; /// /// The AccuRev "principal" (userid) to run under. If not specified, AccuRev will follow its rules /// for determining the principal. /// /// /// Optional, default is to let AccuRev decide who the principal is. Must be specified if "login" is /// set to "true". /// [ReflectorProperty("principal", Required = false)] public string AccuRevPrincipal = null; /// /// Pathname of the root of the AccuRev workspace to update and/or check, either absolute or relative /// to the project working directory. /// /// /// Optional, defaults to the project working directory. /// [ReflectorProperty("workspace", Required = false)] public string Workspace = string.Empty; #endregion private readonly ProcessExecutor _executor; private readonly IHistoryParser _historyParser; internal static readonly string HISTORY_COMMAND_FORMAT_LAST_TRAN = @"hist -a -k promote -s ""{0}"" -t HIGHEST"; public AccuRevLabeller() : this(new AccuRevHistoryParser(), new ProcessExecutor()) {} public AccuRevLabeller(IHistoryParser historyParser, ProcessExecutor executor) { _historyParser = historyParser; _executor = executor; if (LogIn & ((AccuRevPrincipal == null) || (AccuRevPassword == null))) { Log.Error("login=true requires principal= and password= to be specified."); } } public string Generate(IIntegrationResult integrationResult) { PossiblyLogIn(integrationResult); string args = string.Format(HISTORY_COMMAND_FORMAT_LAST_TRAN, GetBasisStreamName(integrationResult)); ProcessInfo histCommand = PrepCommand(args, integrationResult); ProcessResult pResult = Execute(histCommand); Modification[] mods = _historyParser.Parse(new StringReader(pResult.StandardOutput), DateTime.MinValue, DateTime.MaxValue); if (mods.Length == 0) return makeDefaultVersion(); else return mods[0].ChangeNumber.ToString(); } public void Run(IIntegrationResult result) { result.Label = Generate(result); } private static string makeDefaultVersion() { return "0"; } /// /// Determine the AccuRev basis stream name for the specified workspace directory. /// /// the IIntegrationResult object contaiing the directory name /// of the workspace /// the name of the basis stream private string GetBasisStreamName(IIntegrationResult result) { string line; Regex findBasisRegex = new Regex(@"^\s*Basis:\s+(.+)$"); PossiblyLogIn(result); ProcessResult cmdResults = RunCommand("info", result); StringReader infoStdOut = new StringReader(cmdResults.StandardOutput); while ((line = infoStdOut.ReadLine()) != null) { Match parsed = findBasisRegex.Match(line); if (parsed.Success) return parsed.Groups[1].ToString().Trim(); // Format is: "Basis: __stream_name__" } Log.Error(string.Format("No \"Basis:\" line found in output from AccuRev \"accurev info\": {0}", cmdResults.StandardOutput)); return ""; } /// /// Log in to AccuRev if we're supposed to do so. /// /// IntegrationResult for which the command will be run private void PossiblyLogIn(IIntegrationResult result) { if (!LogIn) return; // Nothing to do. if ((AccuRevPrincipal == null) || (AccuRevPassword == null)) { Log.Error("login=true requires principal= and password= to be specified."); return; } RunCommand(string.Format("login {0} \"{1}\"", AccuRevPrincipal, AccuRevPassword), result); } /// /// Prepare an AccuRev command for execution. /// /// arguments for the "accurev" command /// IntegrationResult for which the command will be run /// a ProcessInfo object primed to execute the specified command private ProcessInfo PrepCommand(string args, IIntegrationResult result) { Log.Debug(string.Format("Preparing to run AccuRev command: {0} {1}", Executable, args)); ProcessInfo command = new ProcessInfo(Executable, args, result.BaseFromWorkingDirectory(Workspace)); SetEnvironmentVariables(command.EnvironmentVariables, result); return command; } /// /// Execute an AccuRev command and check the results. /// /// arguments for the "accurev" command /// IntegrationResult for which the command is being run /// a ProcessResult object with the results from the command private ProcessResult RunCommand(string args, IIntegrationResult result) { ProcessInfo command = PrepCommand(args, result); ProcessResult cmdResults = Execute(command); if (cmdResults.Failed) { Log.Error(string.Format("AccuRev command \"{0} {1}\" failed with RC={2}", Executable, args, cmdResults.ExitCode)); if ((cmdResults.StandardError != null) && (cmdResults.StandardError != "")) Log.Error(string.Format("\tError output: {0}", cmdResults.StandardError)); } return cmdResults; } /// /// Make sure this IIntegrationResult object has our environment variables set in it. /// /// The collection of environment variables to be updated. /// IntegrationResult for the command whose variables we are updating. private void SetEnvironmentVariables(StringDictionary environmentVariables, IIntegrationResult result) { if (!StringUtil.IsBlank(AccuRevHomeDir)) environmentVariables["ACCUREV_HOME"] = result.BaseFromArtifactsDirectory(AccuRevHomeDir); if (!StringUtil.IsBlank(AccuRevPrincipal)) environmentVariables["ACCUREV_PRINCIPAL"] = AccuRevPrincipal; } private ProcessResult Execute(ProcessInfo processInfo) { processInfo.TimeOut = 30000; ProcessResult result = _executor.Execute(processInfo); if (result.TimedOut) { throw new CruiseControlException("Source control operation has timed out."); } else if (result.Failed) { throw new CruiseControlException(string.Format("Source control operation failed: {0}. Process command: {1} {2}", result.StandardError, processInfo.FileName, processInfo.Arguments)); } else if (result.HasErrorOutput) { Log.Warning(string.Format("Source control wrote output to stderr: {0}", result.StandardError)); } return result; } } }