private async Task <ResultStatus> StartCommand(IExecuteContext executeContext, ListStore <CommandResultEntry> commandResultEntries, BuilderContext builderContext) { var logger = executeContext.Logger; //await Scheduler.Yield(); ResultStatus status; using (commandResultEntries) { logger.Debug("Starting command {0}...", Command.ToString()); // Creating the CommandResult object var commandContext = new LocalCommandContext(executeContext, this, builderContext); // Actually processing the command if (Command.ShouldSpawnNewProcess() && builderContext.MaxParallelProcesses > 0) { while (!builderContext.CanSpawnParallelProcess()) { await Task.Delay(1, Command.CancellationToken); } var address = "net.pipe://localhost/" + Guid.NewGuid(); var arguments = string.Format("--slave=\"{0}\" --build-path=\"{1}\" --profile=\"{2}\"", address, builderContext.BuildPath, builderContext.BuildProfile); var startInfo = new ProcessStartInfo { FileName = builderContext.SlaveBuilderPath, Arguments = arguments, WorkingDirectory = Environment.CurrentDirectory, CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, }; // Start WCF pipe for communication with process var processBuilderRemote = new ProcessBuilderRemote(commandContext, Command, builderContext.Parameters); var host = new ServiceHost(processBuilderRemote); host.AddServiceEndpoint(typeof(IProcessBuilderRemote), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) { MaxReceivedMessageSize = int.MaxValue }, address); host.Open(); var output = new List <string>(); var process = new Process { StartInfo = startInfo }; process.Start(); process.OutputDataReceived += (_, args) => LockProcessAndAddDataToList(process, output, args); process.ErrorDataReceived += (_, args) => LockProcessAndAddDataToList(process, output, args); process.BeginOutputReadLine(); process.BeginErrorReadLine(); // Attach debugger to newly created process // Add a reference to EnvDTE80 in the csproj and uncomment this (and also the Thread.Sleep in BuildEngineCmmands), then start the master process without debugger to attach to a slave. //var dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.11.0"); //foreach (EnvDTE.Process dteProcess in dte.Debugger.LocalProcesses) //{ // if (dteProcess.ProcessID == process.Id) // { // dteProcess.Attach(); // dte.Debugger.CurrentProcess = dteProcess; // } //} Task[] tasksToWait = null; while (!process.HasExited) { Thread.Sleep(1); lock (spawnedCommandsToWait) { if (spawnedCommandsToWait.Count > 0) { tasksToWait = spawnedCommandsToWait.ToArray(); spawnedCommandsToWait.Clear(); } } if (tasksToWait != null) { await Task.WhenAll(tasksToWait); tasksToWait = null; } } host.Close(); builderContext.NotifyParallelProcessEnded(); if (process.ExitCode != 0) { logger.Debug("Remote command crashed with output:\n{0}", string.Join(Environment.NewLine, output)); } if (processBuilderRemote.Result != null) { // Register results back locally foreach (var outputObject in processBuilderRemote.Result.OutputObjects) { commandContext.RegisterOutput(outputObject.Key, outputObject.Value); } // Register log messages foreach (var logMessage in processBuilderRemote.Result.LogMessages) { commandContext.Logger.Log(logMessage); } // Register tags foreach (var tag in processBuilderRemote.Result.TagSymbols) { TagSymbol tagSymbol; // Resolve tag locally if (!Command.TagSymbols.TryGetValue(tag.Value, out tagSymbol)) { // Should we ignore silently? (with warning) throw new InvalidOperationException("Could not find tag symbol."); } commandContext.AddTag(tag.Key, tagSymbol); } } status = Command.CancellationToken.IsCancellationRequested ? ResultStatus.Cancelled : (process.ExitCode == 0 ? ResultStatus.Successful : ResultStatus.Failed); } else { Command.PreCommand(commandContext); if (!Command.BasePreCommandCalled) { throw new InvalidOperationException("base.PreCommand not called in command " + Command); } try { // Merge results from prerequisites // TODO: This will prevent us from overwriting this asset with different content as it will result in a write conflict // At some point we _might_ want to get rid of WaitBuildStep/ListBuildStep system and write a fully stateless input/output-based system; probably need further discussions var assetIndexMap = ContentManager.FileProvider.AssetIndexMap; foreach (var prerequisiteStep in PrerequisiteSteps) { foreach (var output in prerequisiteStep.OutputObjectIds) { assetIndexMap[output.Key.Path] = output.Value; } } status = await Command.DoCommand(commandContext); } catch (Exception ex) { executeContext.Logger.Error("Exception in command " + this + ": " + ex); status = ResultStatus.Failed; } Command.PostCommand(commandContext, status); if (!Command.BasePostCommandCalled) { throw new InvalidOperationException("base.PostCommand not called in command " + Command); } } // Ensure the command set at least the result status if (status == ResultStatus.NotProcessed) { throw new InvalidDataException("The command " + Command + " returned ResultStatus.NotProcessed after completion."); } // Registering the result to the build cache RegisterCommandResult(commandResultEntries, commandContext.ResultEntry, status); } return(status); }
private async Task<ResultStatus> StartCommand(IExecuteContext executeContext, ListStore<CommandResultEntry> commandResultEntries, BuilderContext builderContext) { var logger = executeContext.Logger; // Register the cancel callback var cancellationTokenSource = executeContext.CancellationTokenSource; cancellationTokenSource.Token.Register(x => ((Command)x).Cancel(), Command); Command.CancellationToken = cancellationTokenSource.Token; //await Scheduler.Yield(); ResultStatus status; using (commandResultEntries) { logger.Debug("Starting command {0}...", Command.ToString()); // Creating the CommandResult object var commandContext = new LocalCommandContext(executeContext, this, builderContext); // Actually processing the command if (Command.ShouldSpawnNewProcess() && builderContext.MaxParallelProcesses > 0) { while (!builderContext.CanSpawnParallelProcess()) { await Task.Delay(1, Command.CancellationToken); } var address = "net.pipe://localhost/" + Guid.NewGuid(); var arguments = string.Format("--slave=\"{0}\" --build-path=\"{1}\" --profile=\"{2}\"", address, builderContext.BuildPath, builderContext.BuildProfile); var startInfo = new ProcessStartInfo { FileName = builderContext.SlaveBuilderPath, Arguments = arguments, WorkingDirectory = Environment.CurrentDirectory, CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, }; // Start WCF pipe for communication with process var processBuilderRemote = new ProcessBuilderRemote(commandContext, Command, builderContext.Parameters); var host = new ServiceHost(processBuilderRemote); host.AddServiceEndpoint(typeof(IProcessBuilderRemote), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) { MaxReceivedMessageSize = int.MaxValue }, address); host.Open(); var output = new List<string>(); var process = new Process { StartInfo = startInfo }; process.Start(); process.OutputDataReceived += (_, args) => LockProcessAndAddDataToList(process, output, args); process.ErrorDataReceived += (_, args) => LockProcessAndAddDataToList(process, output, args); process.BeginOutputReadLine(); process.BeginErrorReadLine(); // Attach debugger to newly created process // Add a reference to EnvDTE80 in the csproj and uncomment this (and also the Thread.Sleep in BuildEngineCmmands), then start the master process without debugger to attach to a slave. //var dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.11.0"); //foreach (EnvDTE.Process dteProcess in dte.Debugger.LocalProcesses) //{ // if (dteProcess.ProcessID == process.Id) // { // dteProcess.Attach(); // dte.Debugger.CurrentProcess = dteProcess; // } //} Task[] tasksToWait = null; while (!process.HasExited) { Thread.Sleep(1); lock (spawnedCommandsToWait) { if (spawnedCommandsToWait.Count > 0) { tasksToWait = spawnedCommandsToWait.ToArray(); spawnedCommandsToWait.Clear(); } } if (tasksToWait != null) { await Task.WhenAll(tasksToWait); tasksToWait = null; } } host.Close(); builderContext.NotifyParallelProcessEnded(); if (process.ExitCode != 0) { logger.Debug("Remote command crashed with output:\n{0}", string.Join(Environment.NewLine, output)); } if (processBuilderRemote.Result != null) { // Register results back locally foreach (var outputObject in processBuilderRemote.Result.OutputObjects) { commandContext.RegisterOutput(outputObject.Key, outputObject.Value); } // Register log messages foreach (var logMessage in processBuilderRemote.Result.LogMessages) { commandContext.Logger.Log(logMessage); } // Register tags foreach (var tag in processBuilderRemote.Result.TagSymbols) { TagSymbol tagSymbol; // Resolve tag locally if (!Command.TagSymbols.TryGetValue(tag.Value, out tagSymbol)) { // Should we ignore silently? (with warning) throw new InvalidOperationException("Could not find tag symbol."); } commandContext.AddTag(tag.Key, tagSymbol); } } status = Command.CancellationToken.IsCancellationRequested ? ResultStatus.Cancelled : (process.ExitCode == 0 ? ResultStatus.Successful : ResultStatus.Failed); } else { Command.PreCommand(commandContext); if (!Command.BasePreCommandCalled) throw new InvalidOperationException("base.PreCommand not called in command " + Command); try { status = await Command.DoCommand(commandContext); } catch (Exception ex) { executeContext.Logger.Error("Exception in command " + this + ": " + ex); status = ResultStatus.Failed; } Command.PostCommand(commandContext, status); if (!Command.BasePostCommandCalled) throw new InvalidOperationException("base.PostCommand not called in command " + Command); } // Ensure the command set at least the result status if (status == ResultStatus.NotProcessed) throw new InvalidDataException("The command " + Command + " returned ResultStatus.NotProcessed after completion."); // Registering the result to the build cache RegisterCommandResult(commandResultEntries, commandContext.ResultEntry, status); } return status; }