Exemple #1
0
        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;
        }