/// <summary>
 /// Cleanup this build step. Cleanup will only be called if this build step ran.
 /// </summary>
 /// <param name="context">Current build context.</param>
 /// <returns>The result of cleaning this build step.</returns>
 public virtual BuildResult Cleanup(BuildContext context) => context.Success();
        /// <summary>
        /// Run all enabled build steps and cleanup.
        /// </summary>
        /// <param name="context">The current build context.</param>
        /// <returns>A build result indicating if successful or not.</returns>
        public BuildResult Run(BuildContext context)
        {
            var results = new List <BuildResult>();
            var title   = context.BuildProgress?.Title ?? string.Empty;

            // Setup build steps list
            var cleanupSteps = new Stack <BuildStepBase>();
            var enabledSteps = m_BuildSteps.Where(step => step.IsEnabled(context)).ToArray();

            // Run build steps and stop on first failure of any kind
            for (var i = 0; i < enabledSteps.Length; ++i)
            {
                var step = enabledSteps[i];

                // Update build progress
                var cancelled = context.BuildProgress?.Update($"{title} (Step {i + 1} of {enabledSteps.Length})", step.Description + "...", (float)i / enabledSteps.Length) ?? false;
                if (cancelled)
                {
                    results.Add(context.Failure($"{title} was cancelled."));
                    break;
                }

                // Add step to cleanup stack only if it overrides implementation
                if (step.GetType().GetMethod(nameof(BuildStepBase.Cleanup)).DeclaringType != typeof(BuildStepBase))
                {
                    cleanupSteps.Push(step);
                }

                // Run step
                try
                {
                    results.Add(step.Run(context));
                    if (results.Last().Failed)
                    {
                        break;
                    }
                }
                catch (Exception exception)
                {
                    results.Add(context.Failure(exception));
                    break;
                }
            }

            // Execute cleanup (even if there are failures):
            // 1) in opposite order of steps that ran.
            // 2) can't be cancelled; cleanup must run.
            foreach (var step in cleanupSteps)
            {
                context.BuildProgress?.Update($"{title} (Cleanup)", step.Description + "...", 1.0F);
                try
                {
                    results.Add(step.Cleanup(context));
                }
                catch (Exception exception)
                {
                    results.Add(context.Failure(exception));
                }
            }

            // Return the first failed result if any
            var failure = results.FirstOrDefault(result => result.Failed);

            return(failure != null ? failure : context.Success());
        }