4 using System.Collections.Generic;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using SiliconStudio.Core.Diagnostics;
10 using SiliconStudio.Core.Serialization.Assets;
11 using SiliconStudio.Core.Storage;
12 using SiliconStudio.Core.IO;
14 using System.Diagnostics;
15 using System.ServiceModel;
17 namespace SiliconStudio.BuildEngine
22 public override string Title {
get {
return Command != null ? Command.Title :
"<No command>"; } }
35 private readonly List<Task> spawnedCommandsToWait =
new List<Task>();
44 return Command.ToString();
55 ObjectId commandHash = Command.ComputeCommandHash(executeContext);
57 var commandResultsFileStream = executeContext.ResultMap.OpenStream(commandHash, VirtualFileMode.OpenOrCreate, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite);
58 var commandResultEntries =
new ListStore<CommandResultEntry>(commandResultsFileStream) { AutoLoadNewValues =
false };
59 commandResultEntries.LoadNewValues();
60 commandResultsFileStream.Close();
62 CommandResultEntry matchingResult = FindMatchingResult(executeContext, commandResultEntries.GetValues());
63 if (matchingResult != null)
67 foreach (KeyValuePair<ObjectUrl, ObjectId> outputObject
in matchingResult.OutputObjects)
69 switch (outputObject.Key.Type)
74 if (
File.Exists(outputObject.Key.Path))
75 File.Delete(outputObject.Key.Path);
79 executeContext.Logger.Error(
"Unable to delete file: " + outputObject.Key.Path);
82 case UrlType.Internal:
83 executeContext.ResultMap.Delete(outputObject.Value);
90 spawnedStep.Clean(executeContext, builderContext, deleteOutput);
94 executeContext.ResultMap.Delete(commandHash);
99 ListStore<CommandResultEntry> commandResultEntries;
103 Monitor.Enter(executeContext);
109 commandHash = Command.ComputeCommandHash(executeContext);
110 var commandResultsFileStream = executeContext.ResultMap.OpenStream(commandHash, VirtualFileMode.OpenOrCreate, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite);
111 commandResultEntries =
new ListStore<CommandResultEntry>(commandResultsFileStream) { AutoLoadNewValues =
false };
112 commandResultEntries.LoadNewValues();
118 var status = ResultStatus.NotProcessed;
120 if (ShouldExecute(executeContext, commandResultEntries.GetValues(), commandHash, out matchingResult))
122 CommandBuildStep stepInProgress = executeContext.IsCommandCurrentlyRunning(commandHash);
123 if (stepInProgress != null)
125 Monitor.Exit(executeContext);
126 executeContext.Logger.Debug(
"Command {0} delayed because it is currently running...", Command.ToString());
127 status = (await stepInProgress.ExecutedAsync()).Status;
128 matchingResult = stepInProgress.Result;
132 executeContext.NotifyCommandBuildStepStarted(
this, commandHash);
133 Monitor.Exit(executeContext);
135 executeContext.Logger.Debug(
"Command {0} scheduled...", Command.ToString());
137 status = await StartCommand(executeContext, commandResultEntries, builderContext);
138 executeContext.NotifyCommandBuildStepFinished(
this, commandHash);
143 Monitor.Exit(executeContext);
147 if (matchingResult != null)
149 using (commandResultEntries)
152 executeContext.Logger.Verbose(
"Command {0} is up-to-date, skipping...", Command.ToString());
155 Debug.Assert(SpawnedStepsList.Count == 0);
160 SpawnedStepsList.Add(spawnedStep);
161 executeContext.ScheduleBuildStep(spawnedStep);
167 await Task.WhenAll(SpawnedSteps.Select(x => x.ExecutedAsync()));
169 status = ResultStatus.NotTriggeredWasSuccessful;
171 RegisterCommandResult(commandResultEntries, matchingResult, status);
181 SpawnedStepsList.Add(spawnedStep);
183 executeContext.ScheduleBuildStep(spawnedStep);
184 var resultStatus = (await spawnedStep.ExecutedAsync()).Status;
189 private void RegisterCommandResult(ListStore<CommandResultEntry> commandResultEntries, CommandResultEntry result,
ResultStatus status)
201 commandResultEntries.AddValue(result);
205 internal bool ShouldExecute(IExecuteContext executeContext, CommandResultEntry[] previousResultCollection,
ObjectId commandHash, out CommandResultEntry matchingResult)
207 IndexFileCommand.MountDatabases(executeContext);
210 matchingResult = FindMatchingResult(executeContext, previousResultCollection);
214 IndexFileCommand.UnmountDatabases(executeContext);
217 if (matchingResult == null || Command.ShouldForceExecution())
220 matchingResult = null;
227 internal CommandResultEntry FindMatchingResult(IPrepareContext prepareContext, CommandResultEntry[] commandResultCollection)
229 if (commandResultCollection == null)
235 foreach (CommandResultEntry entry
in commandResultCollection)
237 bool entryMatch =
true;
239 foreach (var inputDepVersion
in entry.InputDependencyVersions)
241 var hash = prepareContext.ComputeInputHash(inputDepVersion.Key.Type, inputDepVersion.Key.Path);
242 if (hash != inputDepVersion.Value)
268 private async
Task<ResultStatus> StartCommand(IExecuteContext executeContext, ListStore<CommandResultEntry> commandResultEntries, BuilderContext builderContext)
270 var logger = executeContext.Logger;
273 var cancellationTokenSource = executeContext.CancellationTokenSource;
274 cancellationTokenSource.Token.Register(x => ((Command)x).Cancel(), Command);
276 Command.CancellationToken = cancellationTokenSource.Token;
282 using (commandResultEntries)
284 logger.Debug(
"Starting command {0}...", Command.ToString());
287 var commandContext =
new LocalCommandContext(executeContext,
this, builderContext);
290 if (Command.ShouldSpawnNewProcess() && builderContext.MaxParallelProcesses > 0)
292 while (!builderContext.CanSpawnParallelProcess())
294 await Task.Delay(1, Command.CancellationToken);
297 var address =
"net.pipe://localhost/" + Guid.NewGuid();
298 var arguments = string.Format(
"--slave=\"{0}\" --build-path=\"{1}\" --profile=\"{2}\"", address, builderContext.BuildPath, builderContext.BuildProfile);
300 var startInfo =
new ProcessStartInfo
302 FileName = builderContext.SlaveBuilderPath,
303 Arguments = arguments,
304 WorkingDirectory = Environment.CurrentDirectory,
305 CreateNoWindow =
true,
306 UseShellExecute =
false,
307 RedirectStandardOutput =
true,
308 RedirectStandardError =
true,
312 var processBuilderRemote =
new ProcessBuilderRemote(commandContext, Command, builderContext.Parameters);
313 var host =
new ServiceHost(processBuilderRemote);
314 host.AddServiceEndpoint(typeof(IProcessBuilderRemote),
new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) { MaxReceivedMessageSize = int.MaxValue }, address);
317 var output =
new List<string>();
319 var process =
new Process { StartInfo = startInfo };
321 process.OutputDataReceived += (_, args) => LockProcessAndAddDataToList(process, output, args);
322 process.ErrorDataReceived += (_, args) => LockProcessAndAddDataToList(process, output, args);
323 process.BeginOutputReadLine();
324 process.BeginErrorReadLine();
338 Task[] tasksToWait = null;
340 while (!process.HasExited)
343 lock (spawnedCommandsToWait)
345 if (spawnedCommandsToWait.Count > 0)
347 tasksToWait = spawnedCommandsToWait.ToArray();
348 spawnedCommandsToWait.Clear();
352 if (tasksToWait != null)
354 await Task.WhenAll(tasksToWait);
360 builderContext.NotifyParallelProcessEnded();
362 if (process.ExitCode != 0)
364 logger.Debug(
"Remote command crashed with output:\n{0}", string.Join(Environment.NewLine, output));
367 if (processBuilderRemote.Result != null)
370 foreach (var outputObject
in processBuilderRemote.Result.OutputObjects)
372 commandContext.RegisterOutput(outputObject.Key, outputObject.Value);
376 foreach (var tag
in processBuilderRemote.Result.TagSymbols)
381 if (!Command.TagSymbols.TryGetValue(tag.Value, out tagSymbol))
384 throw new InvalidOperationException(
"Could not find tag symbol.");
387 commandContext.AddTag(tag.Key, tagSymbol);
391 status = Command.CancellationToken.IsCancellationRequested ? ResultStatus.Cancelled : (process.ExitCode == 0 ? ResultStatus.Successful : ResultStatus.Failed);
395 Command.PreCommand(commandContext);
396 if (!Command.BasePreCommandCalled)
397 throw new InvalidOperationException(
"base.PreCommand not called in command " + Command);
399 status = await Command.DoCommand(commandContext);
401 Command.PostCommand(commandContext, status);
402 if (!Command.BasePostCommandCalled)
403 throw new InvalidOperationException(
"base.PostCommand not called in command " + Command);
408 throw new InvalidDataException(
"The command " + Command +
" returned ResultStatus.NotProcessed after completion.");
411 RegisterCommandResult(commandResultEntries, commandContext.ResultEntry, status);
417 private static void LockProcessAndAddDataToList(Process process, List<string> output, DataReceivedEventArgs args)
419 if (!
string.IsNullOrEmpty(args.Data))
423 output.Add(args.Data);
430 lock (spawnedCommandsToWait)
432 spawnedCommandsToWait.Add(task);
Virtual abstraction over a file system. It handles access to files, http, packages, path rewrite, etc...
CommandResultEntry Result
Command Result, set only after step completion. Not thread safe, should not be modified ...
static string BuildUrl(string vfsRootUrl, ObjectId objectId)
static bool FileExists(string path)
Checks the existence of a file.
void AwaitSpawnedCommand(Task< ResultStatus > task)
ResultStatus
Status of a command.
override string ToString()
Object Database Backend (ODB) implementation using VirtualFileSystem
override void Clean(IExecuteContext executeContext, BuilderContext builderContext, bool deleteOutput)
Clean the build, deleting the command cache which is used to determine wheither a command has already...
A hash to uniquely identify data.
CommandBuildStep(Command command)
List< Command > SpawnedCommands
Commands created during the execution of the current command.
override async Task< ResultStatus > Execute(IExecuteContext executeContext, BuilderContext builderContext)
Execute the BuildStep, usually resulting in scheduling tasks in the scheduler
override BuildStep Clone()
Clone this Build Step.