/// <summary> /// Initializes the <see cref="SynergyConnectionInfo.Delimiter"/> and /// <see cref="SynergyProjectInfo.ObjectName"/>, and /// the <see cref="SynergyProjectInfo.WorkAreaPath"/> fields. /// </summary> /// <exception cref="CruiseControlException"> /// If the <see cref="SynergyCommandBuilder.GetDelimiter"/> or /// <see cref="SynergyCommandBuilder.GetDcmDelimiter"/> commands fail /// to return a stdout stream. Also called if the work area is an invalid /// path, which can happen if the project has not been synchronized on /// the client machine. /// </exception> private void Initialize() { ProcessInfo info; ProcessResult result; string temp; try { // set the delimiter for the database info = SynergyCommandBuilder.GetDelimiter(connection); info.EnvironmentVariables[SessionToken] = connection.SessionId; result = Execute(info); temp = result.StandardOutput; if (temp.Length == 0) { throw(new CruiseControlException("Failed to read the CM Synergy delimiter")); } connection.Delimiter = temp[0]; } catch (Exception inner) { throw(new CruiseControlException("Failed to read the CM Synergy database delimiter", inner)); } try { // set the project spec info = SynergyCommandBuilder.GetProjectFullName(connection, project); info.EnvironmentVariables[SessionToken] = connection.SessionId; result = Execute(info); temp = result.StandardOutput.Trim(); project.ObjectName = temp; } catch (Exception inner) { temp = String.Concat(@"CM Synergy Project """, project.ProjectSpecification, @""" not found"); throw(new CruiseControlException(temp, inner)); } try { // read the project work area path, to use for the working directory for ccm commands info = SynergyCommandBuilder.GetWorkArea(connection, project); info.EnvironmentVariables[SessionToken] = connection.SessionId; result = Execute(info); project.WorkAreaPath = Path.GetFullPath(result.StandardOutput.Trim()); if (!Directory.Exists(project.WorkAreaPath)) { throw(new CruiseControlException(String.Concat("CM Synergy work area '", result.StandardOutput.Trim(), "' not found."))); } Log.Info(String.Concat(project.ProjectSpecification, " work area is '", project.WorkAreaPath, "'")); } catch (Exception inner) { temp = String.Concat(@"CM Synergy Work Area for Project """, project.ProjectSpecification, @""" could not be determined."); throw(new CruiseControlException(temp, inner)); } }
/// <summary> /// If enabled, discards changes to specified work area paths. /// </summary> /// <remarks> /// Supports both file and directory paths. Useful if you build process /// emits artifacts under source control. Changes to controlled files can cause /// reconfigure commands to fail. /// </remarks> private void Reconcile() { // force a connection to be established, if it hasn't already // need in case of a forced build command.Execute(SynergyCommandBuilder.Heartbeat(connection)); if (null != project.ReconcilePaths) { string fullPath; foreach (string path in project.ReconcilePaths) { // normalize the path if (!Path.IsPathRooted(path)) { fullPath = Path.Combine(project.WorkAreaPath, path); } else { fullPath = path; } fullPath = Path.GetFullPath(fullPath); Log.Info(String.Concat("Reconciling work area path '", path, "'")); // sync the work area to discard work area changes command.Execute(SynergyCommandBuilder.Reconcile(connection, project, path)); } } }
/// <summary> /// Stops the Synergy session, if one was previously opened. /// </summary> private void Close() { ProcessInfo info; if (isOpen) { info = SynergyCommandBuilder.Stop(connection); // set the session id for ccm.exe to use info.EnvironmentVariables[SessionToken] = connection.SessionId; //Make sure the thread has a name so the ProcessExecuter will not crash if (string.IsNullOrEmpty(Thread.CurrentThread.Name)) { Thread.CurrentThread.Name = connection.SessionId; } /* This should be a fire-and-forget call. * We don't want an exception thrown if the session cannot be stopped. */ /* don't call this.Execute(), as it will cause an infinite loop * once this.ValidateSession is called. */ executor.Execute(info); // reset the CCM_ADDR value and delimiter fields connection.Reset(); } // reset the open flag isOpen = false; }
/// <summary> /// Gets the date of the project's last reconfigure time to ensure consistency. /// </summary> /// <exception cref="CruiseControlException"> /// Thrown if the last reconfigure time cannot be read or parsed successfully. /// </exception> /// <returns></returns> private DateTime GetReconfigureTime() { // setup the project to reconfigure using the default template ProcessResult result = command.Execute(SynergyCommandBuilder.GetLastReconfigureTime(connection, project)); try { return(DateTime.Parse(result.StandardOutput.Trim(), CultureInfo.InvariantCulture)); } catch (Exception inner) { throw(new CruiseControlException("Failed to read the project's last reconfigure time.", inner)); } }
/// <summary> /// Performs a CM Synergy "Reconfigure"/"Update Members" for a forced build. /// </summary> /// <remarks> /// <see cref="GetModifications"/> will also reconfigure when modifications are detected /// which explains why this method is a no-op unless we have a forced build. /// </remarks> /// <param name="integration">Not used.</param> /// <url>element://model:project::CCNet.Synergy.Plugin/design:view:::ax60xur0dt7rg6h_v</url> public void GetSource(IIntegrationResult integration) { // reconcile any work area paths specificed by the config file Reconcile(); // reconfigure the project ProcessInfo info = SynergyCommandBuilder.Reconfigure(connection, project); command.Execute(info); /* UNDO -- refactored out - no longer need to count the number of objects replaced * int objectsReplaced = SynergyParser.GetReconfigureCount(processResult.StandardOutput); */ // update the timestamp of the reconfigure project.LastReconfigureTime = GetReconfigureTime(); }
/// <summary> /// Guarantees that a Synergy session is open, alive, and usable. /// </summary> private void ValidateSession() { // check that we have a CCM Address bool isValid = (null != connection.SessionId && connection.SessionId.Length > 0); // if the connection is open, execute the heartbeat command // to ensure that the server connection was not lost if (isOpen && isValid) { ProcessInfo info = SynergyCommandBuilder.Heartbeat(connection); if (null != project && null != project.WorkAreaPath && project.WorkAreaPath.Length > 0) { info = new ProcessInfo(info.FileName, info.Arguments, project.WorkAreaPath); } // set the session id for ccm.exe to use if (null != connection && null != connection.SessionId && connection.SessionId.Length > 0) { info.EnvironmentVariables[SessionToken] = connection.SessionId; } /* don't call this.Execute(), as it will cause an infinite loop * once this.ValidateSession is called. */ ProcessResult result = executor.Execute(info); // reset the valid flag, if ccm status does not report the session token isValid = IsSessionAlive(result.StandardOutput, connection.SessionId, connection.Database); if (!isValid) { // Call the close method, since there's likely a problem with the client/server // connection. This call will not throw an exception even if it fails. Close(); } } // (re-)open the session if one could not be found, or if it was killed if (!isValid) { // Now try to re-establish a connection Open(); } }
private Modification[] GetModifications(DateTime from) { Modification[] modifications = new Modification[0]; if (project.TemplateEnabled) { // setup the project to reconfigure using the default template command.Execute(SynergyCommandBuilder.UseReconfigureTemplate(connection, project)); } // refresh the query based folders in the reconfigure properties command.Execute(SynergyCommandBuilder.UpdateReconfigureProperites(connection, project)); // this may fail, if a build was forced, and no changes were found ProcessResult result = command.Execute(SynergyCommandBuilder.GetNewTasks(connection, project, from), false); if (!result.Failed) { // cache the output of the task/comment query string comments = result.StandardOutput; // populate the selection set with the objects associated with the detected tasks result = command.Execute(SynergyCommandBuilder.GetTaskObjects(connection, project), false); if (!result.Failed) { // Get the path information for each object associated with the tasks result = command.Execute(SynergyCommandBuilder.GetObjectPaths(connection, project), false); if (!result.Failed) { modifications = parser.Parse(comments, result.StandardOutput, from); if (null != urlBuilder) { urlBuilder.SetupModification(modifications); } } } } FillIssueUrl(modifications); return(modifications); }
/// <summary> /// Stops the Synergy session, if one was previously opened. /// </summary> private void Close() { ProcessInfo info; if (isOpen) { info = SynergyCommandBuilder.Stop(connection); /* This should be a fire-and-forget call. * We don't want an exception thrown if the session cannot be stopped. */ /* don't call this.Execute(), as it will cause an infinite loop * once this.ValidateSession is called. */ executor.Execute(info); // reset the CCM_ADDR value and delimiter fields connection.Reset(); } // reset the open flag isOpen = false; }
/// <summary> /// Adds tasks to a shared task folder, if configured, and creates a baseline /// if requested by the configuration. /// </summary> /// <remarks> /// <note type="implementnotes"> /// This method makes use of CM Synergy selection commands, in order to pipe the /// results of one query to another command. If the CM Synergy session is lost /// during the course of execution in this method, the selection set is also lost. /// An exception will be thrown when the next CM Synergy command is executed, /// because the selection set is empty. This should be a very rare case, and the /// performance gains of piping resultsets are worthwhile. /// </note> /// </remarks> /// <exception cref="CruiseControlException"> /// Thrown if an external process has reconfigured the project since /// <see cref="GetModifications(ThoughtWorks.CruiseControl.Core.IIntegrationResult, ThoughtWorks.CruiseControl.Core.IIntegrationResult)"/> was called. /// </exception> /// <param name="result">Not used.</param> /// <url>element://model:project::CCNet.Synergy.Plugin/design:view:::ow43bejw6wm4was_v</url> public override void LabelSourceControl(IIntegrationResult result) { if (result.Succeeded) { DateTime currentReconfigureTime = GetReconfigureTime(); if (currentReconfigureTime != project.LastReconfigureTime) { string message = String.Format(CultureInfo.CurrentCulture, @"Invalid project state. Cannot add tasks to shared folder '{0}' because " + @"the integration project '{1}' was internally reconfigured at '{2}' " + @"and externally reconfigured at '{3}'. Projects cannot be reconfigured " + @"during an integration run.", project.TaskFolder, project.ProjectSpecification, project.LastReconfigureTime, currentReconfigureTime); throw(new CruiseControlException(message)); } /* Populate the query selection set with a list of ALL tasks * not in the manual folder. This includes all tasks for this integration, * and any prior failed integrations. * We find these by passing the the maximum range of dates to GetModifications */ result.Modifications = GetModifications(DateTime.MinValue); // skip this step if a build was forced, and no changes were found if (null != result.Modifications && result.Modifications.Length > 0) { // comment those tasks with the "label", for both shared folders and baselines command.Execute(SynergyCommandBuilder.AddLabelToTaskComment(connection, project, result)); // append tasks to the shared folder, if one was specified if (SynergyProjectInfo.DefaultTaskFolder != project.TaskFolder) { // append those tasks in the selection set to the shared build folder command.Execute(SynergyCommandBuilder.AddTasksToFolder(connection, project, result)); } } // create a baseline, if requested if (project.BaseliningEnabled) { command.Execute(SynergyCommandBuilder.CreateBaseline(connection, project, result)); } } }
/// <summary> /// Starts a new Synergy session. /// </summary> /// <exception cref="CruiseControlException"> /// Thrown if <see cref="SynergyCommandBuilder.Start"/> fails to write /// a single line containing the CCM_ADDR value. /// </exception> private void Open() { ProcessInfo info; ProcessResult result; int originalTimeout; string temp; string message; if (!isOpen) { info = SynergyCommandBuilder.Start(connection, project); /* Serialize the calls to <c>ccm.exe start</c>, as the CM Synergy * client does not properly support concurrent calls to this command for the * same CM Synergy username. * * TODO: Should we optimize this to have shared locks only for the same CM Synergy * username? It's doutbful that large projects would have different credentials, * so there may be little gain for this functionality */ Log.Debug("Queued for critical section to open CM Synergy session; blocking until lock is acquired."); lock (PadLock) { Log.Debug("Acquired lock to open a session"); /* Don't call this.Execute(), as it will cause an infinite loop * once this.ValidateSession is called. */ result = executor.Execute(info); Log.Debug("Releasing lock to open a session"); } if (result.TimedOut) { message = String.Format(@"Synergy connection timed out after {0} seconds.", connection.Timeout); throw(new CruiseControlException(message)); } // suspend the thread if the database is protected, and the sleep option is enabled if (result.Failed) { if (connection.PollingEnabled) { if (IsDatabaseProtected(result.StandardError, connection.Host, connection.Database)) { // sleep the thread for timeout until the database is unprotected Log.Warning(String.Format("Database {0} on Host {1} Is Protected. Waiting 60 seconds to reconnect.", connection.Host, connection.Database)); Thread.Sleep(new TimeSpan(0, 1, 0)); // save the original timeout originalTimeout = connection.Timeout; // decrement the time to wait for the backup to complete, so that we don't wait forever connection.Timeout -= 60; //recursively call the open function Open(); // revent the timeout prior to recursion connection.Timeout = originalTimeout; // exit the call stack, breaking out of the recursive loop return; } } } temp = result.StandardOutput; if (null != temp && temp.Length > 0) { connection.SessionId = temp.Trim(); Log.Info(String.Concat("CCM_ADDR set to '", connection.SessionId, "'")); } else { throw(new CruiseControlException("CM Synergy logon failed")); } // read the delimiter fields Initialize(); // update the release setting of the project and all subprojects info = SynergyCommandBuilder.GetSubProjects(connection, project); info.EnvironmentVariables[SessionToken] = connection.SessionId; executor.Execute(info); info = SynergyCommandBuilder.SetProjectRelease(connection, project); info.EnvironmentVariables[SessionToken] = connection.SessionId; executor.Execute(info); isOpen = true; } }