diff --git a/source/Nuke.Common/Execution/BuildExtensionAttributeBase.cs b/source/Nuke.Common/Execution/BuildExtensionAttributeBase.cs index 9d2fbc121..ae12d9db0 100644 --- a/source/Nuke.Common/Execution/BuildExtensionAttributeBase.cs +++ b/source/Nuke.Common/Execution/BuildExtensionAttributeBase.cs @@ -10,7 +10,7 @@ namespace Nuke.Common.Execution { public interface IBuildExtension { - void Execute(NukeBuild build, IReadOnlyCollection executableTargets); + void Execute(NukeBuild build, IReadOnlyCollection executableTargets, IReadOnlyCollection executionPlan); } public interface IPreLogoBuildExtension : IBuildExtension diff --git a/source/Nuke.Common/Execution/BuildManager.cs b/source/Nuke.Common/Execution/BuildManager.cs index 2a4079be1..1852183fd 100644 --- a/source/Nuke.Common/Execution/BuildManager.cs +++ b/source/Nuke.Common/Execution/BuildManager.cs @@ -60,11 +60,12 @@ public static int Execute(Expression> defaultTargetExpression Logger.Info($"NUKE Execution Engine {typeof(BuildManager).Assembly.GetInformationalText()}"); Logger.Normal(); - build.ExecuteExtensions(); build.ExecutionPlan = ExecutionPlanner.GetExecutionPlan( build.ExecutableTargets, ParameterService.Instance.GetParameter(() => build.InvokedTargets) ?? ParameterService.Instance.GetPositionalCommandLineArguments(separator: Constants.TargetsSeparator.Single())); + + build.ExecuteExtensions(); CancellationHandler += Finish; InjectionUtility.InjectValues(build, x => !x.IsFast); diff --git a/source/Nuke.Common/Execution/CheckBuildProjectConfigurationsAttribute.cs b/source/Nuke.Common/Execution/CheckBuildProjectConfigurationsAttribute.cs index af3c42c19..8b1798a2a 100644 --- a/source/Nuke.Common/Execution/CheckBuildProjectConfigurationsAttribute.cs +++ b/source/Nuke.Common/Execution/CheckBuildProjectConfigurationsAttribute.cs @@ -19,7 +19,10 @@ public class CheckBuildProjectConfigurationsAttribute : Attribute, IPostLogoBuil { public int TimeoutInMilliseconds { get; set; } = 500; - public void Execute(NukeBuild build, IReadOnlyCollection executableTargets) + public void Execute( + NukeBuild build, + IReadOnlyCollection executableTargets, + IReadOnlyCollection executionPlan) { ControlFlow.AssertWarn(Task.Run(CheckConfiguration).Wait(TimeoutInMilliseconds), $"Could not complete checking build configurations within {TimeoutInMilliseconds} milliseconds."); diff --git a/source/Nuke.Common/Execution/CheckPathEnvironmentVariableAttribute.cs b/source/Nuke.Common/Execution/CheckPathEnvironmentVariableAttribute.cs index 7bdeb4c76..11aed0476 100644 --- a/source/Nuke.Common/Execution/CheckPathEnvironmentVariableAttribute.cs +++ b/source/Nuke.Common/Execution/CheckPathEnvironmentVariableAttribute.cs @@ -14,7 +14,10 @@ namespace Nuke.Common.Execution [AttributeUsage(AttributeTargets.Class)] public class CheckPathEnvironmentVariableAttribute : Attribute, IPostLogoBuildExtension { - public void Execute(NukeBuild build, IReadOnlyCollection executableTargets) + public void Execute( + NukeBuild build, + IReadOnlyCollection executableTargets, + IReadOnlyCollection executionPlan) { ProcessTasks.CheckPathEnvironmentVariable(); } diff --git a/source/Nuke.Common/Execution/ExecutionPlanHtmlService.cs b/source/Nuke.Common/Execution/ExecutionPlanHtmlService.cs index 2d32a78d6..c6330a2ed 100644 --- a/source/Nuke.Common/Execution/ExecutionPlanHtmlService.cs +++ b/source/Nuke.Common/Execution/ExecutionPlanHtmlService.cs @@ -65,7 +65,10 @@ private static string GetEvents(IReadOnlyCollection executable var builder = new StringBuilder(); // When not hovering anything, highlight the default plan - var defaultPlan = ExecutionPlanner.GetExecutionPlan(executableTargets, new[] { executableTargets.Single(x => x.IsDefault).Name }); + var defaultTarget = executableTargets.SingleOrDefault(x => x.IsDefault); + var defaultPlan = defaultTarget != null + ? ExecutionPlanner.GetExecutionPlan(executableTargets, new[] { defaultTarget?.Name }) + : new ExecutableTarget[0]; defaultPlan.ForEach(x => builder.AppendLine($@" $(""#{x.Name}"").addClass('highlight');")); foreach (var executableTarget in executableTargets) diff --git a/source/Nuke.Common/Execution/ExecutionPlanner.cs b/source/Nuke.Common/Execution/ExecutionPlanner.cs index 722d6b17a..f43c3aaa6 100644 --- a/source/Nuke.Common/Execution/ExecutionPlanner.cs +++ b/source/Nuke.Common/Execution/ExecutionPlanner.cs @@ -51,10 +51,11 @@ private static IReadOnlyCollection GetExecutionPlanInternal( var cycles = scc.DetectCycle(graphAsList).Cycles().ToList(); if (cycles.Count > 0) { - ControlFlow.Fail( + Logger.Error( new[] { "Circular dependencies between targets:" } .Concat(cycles.Select(x => $" - {x.Select(y => y.Value.Name).JoinComma()}")) .JoinNewLine()); + Environment.Exit(exitCode: -1); } while (graphAsList.Any()) @@ -62,10 +63,11 @@ private static IReadOnlyCollection GetExecutionPlanInternal( var independents = graphAsList.Where(x => !graphAsList.Any(y => y.Dependencies.Contains(x))).ToList(); if (EnvironmentInfo.ArgumentSwitch("strict") && independents.Count > 1) { - ControlFlow.Fail( - new[] { "Incomplete target definition order." } + Logger.Error( + new[]{"Incomplete target definition order."} .Concat(independents.Select(x => $" - {x.Value.Name}")) .JoinNewLine()); + Environment.Exit(exitCode: -1); } var independent = independents.First(); @@ -100,7 +102,13 @@ private static ExecutableTarget GetExecutableTarget( { var executableTarget = executableTargets.SingleOrDefault(x => x.Name.EqualsOrdinalIgnoreCase(targetName)); if (executableTarget == null) - ControlFlow.Fail($"Target with name '{targetName}' is not available."); + { + Logger.Error( + new[] { $"Target with name '{targetName}' does not exist. Available targets are:" } + .Concat(executableTargets.Select(x => $" - {x.Name}").OrderBy(x => x)) + .JoinNewLine()); + Environment.Exit(exitCode: -1); + } return executableTarget; } @@ -108,10 +116,7 @@ private static ExecutableTarget GetExecutableTarget( private static ExecutableTarget[] GetDefaultTarget(IReadOnlyCollection executableTargets) { var target = executableTargets.SingleOrDefault(x => x.IsDefault); - if (target == null) - Fail("No target has been marked to be the default.", executableTargets); - - return new[] { target }; + return target != null ? new[] { target } : new ExecutableTarget[0]; } private static void Fail(string message, IReadOnlyCollection executableTargets) diff --git a/source/Nuke.Common/Execution/HandleHelpRequestAttribute.cs b/source/Nuke.Common/Execution/HandleHelpRequestAttribute.cs new file mode 100644 index 000000000..a99d8e8d1 --- /dev/null +++ b/source/Nuke.Common/Execution/HandleHelpRequestAttribute.cs @@ -0,0 +1,28 @@ +// Copyright 2019 Maintainers of NUKE. +// Distributed under the MIT License. +// https://github.com/nuke-build/nuke/blob/master/LICENSE + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Nuke.Common.Execution +{ + [AttributeUsage(AttributeTargets.Class)] + internal class HandleHelpRequestAttribute : Attribute, IPostLogoBuildExtension + { + public void Execute( + NukeBuild build, + IReadOnlyCollection executableTargets, + IReadOnlyCollection executionPlan) + { + if (!NukeBuild.Help && executionPlan.Count > 0) + return; + + Logger.Normal(HelpTextService.GetTargetsText(build.ExecutableTargets)); + Logger.Normal(HelpTextService.GetParametersText(build)); + + Environment.Exit(exitCode: 0); + } + } +} diff --git a/source/Nuke.Common/Execution/HandleHelpRequestsAttribute.cs b/source/Nuke.Common/Execution/HandleHelpRequestsAttribute.cs deleted file mode 100644 index 1f19d825d..000000000 --- a/source/Nuke.Common/Execution/HandleHelpRequestsAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2019 Maintainers of NUKE. -// Distributed under the MIT License. -// https://github.com/nuke-build/nuke/blob/master/LICENSE - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Nuke.Common.Execution -{ - [AttributeUsage(AttributeTargets.Class)] - internal class HandleHelpRequestsAttribute : Attribute, IPostLogoBuildExtension - { - public void Execute(NukeBuild build, IReadOnlyCollection executableTargets) - { - if (NukeBuild.Help) - { - Logger.Normal(HelpTextService.GetTargetsText(build.ExecutableTargets)); - Logger.Normal(HelpTextService.GetParametersText(build)); - } - - if (NukeBuild.Plan) - ExecutionPlanHtmlService.ShowPlan(build.ExecutableTargets); - - if (NukeBuild.Help || NukeBuild.Plan) - Environment.Exit(exitCode: 0); - } - } -} diff --git a/source/Nuke.Common/Execution/HandlePlanRequestAttribute.cs b/source/Nuke.Common/Execution/HandlePlanRequestAttribute.cs new file mode 100644 index 000000000..24863d4e9 --- /dev/null +++ b/source/Nuke.Common/Execution/HandlePlanRequestAttribute.cs @@ -0,0 +1,26 @@ +// Copyright 2019 Maintainers of NUKE. +// Distributed under the MIT License. +// https://github.com/nuke-build/nuke/blob/master/LICENSE + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Nuke.Common.Execution +{ + [AttributeUsage(AttributeTargets.Class)] + internal class HandlePlanRequestAttribute : Attribute, IPostLogoBuildExtension + { + public void Execute( + NukeBuild build, + IReadOnlyCollection executableTargets, + IReadOnlyCollection executionPlan) + { + if (!NukeBuild.Plan) + return; + + ExecutionPlanHtmlService.ShowPlan(build.ExecutableTargets); + Environment.Exit(exitCode: 0); + } + } +} diff --git a/source/Nuke.Common/Execution/HandleShellCompletionAttribute.cs b/source/Nuke.Common/Execution/HandleShellCompletionAttribute.cs index 76fde0792..1a9c68b53 100644 --- a/source/Nuke.Common/Execution/HandleShellCompletionAttribute.cs +++ b/source/Nuke.Common/Execution/HandleShellCompletionAttribute.cs @@ -12,7 +12,10 @@ namespace Nuke.Common.Execution [AttributeUsage(AttributeTargets.Class)] internal class HandleShellCompletionAttribute : Attribute, IPreLogoBuildExtension { - public void Execute(NukeBuild build, IReadOnlyCollection executableTargets) + public void Execute( + NukeBuild build, + IReadOnlyCollection executableTargets, + IReadOnlyCollection executionPlan) { var completionItems = new SortedDictionary(); diff --git a/source/Nuke.Common/Execution/HandleVisualStudioDebuggingAttribute.cs b/source/Nuke.Common/Execution/HandleVisualStudioDebuggingAttribute.cs index 3d1eb9671..780224515 100644 --- a/source/Nuke.Common/Execution/HandleVisualStudioDebuggingAttribute.cs +++ b/source/Nuke.Common/Execution/HandleVisualStudioDebuggingAttribute.cs @@ -18,7 +18,10 @@ public class HandleVisualStudioDebuggingAttribute : Attribute, IPreLogoBuildExte { public int TimeoutInMilliseconds { get; } = 10_000; - public void Execute(NukeBuild build, IReadOnlyCollection executableTargets) + public void Execute( + NukeBuild build, + IReadOnlyCollection executableTargets, + IReadOnlyCollection executionPlan) { if (!ParameterService.Instance.GetParameter(Constants.VisualStudioDebugParameterName)) return; diff --git a/source/Nuke.Common/Execution/HelpTextService.cs b/source/Nuke.Common/Execution/HelpTextService.cs index 7173fc8fc..463699eef 100644 --- a/source/Nuke.Common/Execution/HelpTextService.cs +++ b/source/Nuke.Common/Execution/HelpTextService.cs @@ -37,7 +37,7 @@ public static string GetTargetsText(IReadOnlyCollection execut public static string GetParametersText(NukeBuild build) { - var defaultTarget = build.ExecutableTargets.Single(x => x.IsDefault); + var defaultTarget = build.ExecutableTargets.SingleOrDefault(x => x.IsDefault); var builder = new StringBuilder(); var parameters = InjectionUtility.GetParameterMembers(build.GetType()) @@ -50,7 +50,7 @@ void PrintParameter(MemberInfo parameter) var description = SplitLines( // TODO: remove ParameterService.Instance.GetParameterDescription(parameter) - ?.Replace("{default_target}", defaultTarget.Name).Append(".") + ?.Replace("{default_target}", defaultTarget?.Name).Append(".") ?? ""); var parameterName = ParameterService.Instance.GetParameterName(parameter).SplitCamelHumpsWithSeparator("-"); builder.AppendLine($" --{parameterName.PadRight(padRightParameter)} {description.First()}"); diff --git a/source/Nuke.Common/Execution/UnsetVisualStudioEnvironmentVariablesAttribute.cs b/source/Nuke.Common/Execution/UnsetVisualStudioEnvironmentVariablesAttribute.cs index a4d5dc149..e7d576f13 100644 --- a/source/Nuke.Common/Execution/UnsetVisualStudioEnvironmentVariablesAttribute.cs +++ b/source/Nuke.Common/Execution/UnsetVisualStudioEnvironmentVariablesAttribute.cs @@ -14,7 +14,10 @@ namespace Nuke.Common.Execution [AttributeUsage(AttributeTargets.Class)] public class UnsetVisualStudioEnvironmentVariablesAttribute : Attribute, IPreLogoBuildExtension { - public void Execute(NukeBuild build, IReadOnlyCollection executableTargets) + public void Execute( + NukeBuild build, + IReadOnlyCollection executableTargets, + IReadOnlyCollection executionPlan) { new[] { diff --git a/source/Nuke.Common/NukeBuild.cs b/source/Nuke.Common/NukeBuild.cs index 79f9f283c..bde8f4a04 100644 --- a/source/Nuke.Common/NukeBuild.cs +++ b/source/Nuke.Common/NukeBuild.cs @@ -28,13 +28,13 @@ namespace Nuke.Common /// class DefaultBuild : NukeBuild /// { /// public static int Main () => Execute<DefaultBuild>(x => x.Compile); - /// + /// /// Target Clean => _ => _ /// .Executes(() => /// { /// EnsureCleanDirectory(OutputDirectory); /// }); - /// + /// /// Target Compile => _ => _ /// .DependsOn(Clean) /// .Executes(() => @@ -45,7 +45,8 @@ namespace Nuke.Common /// /// [PublicAPI] - [HandleHelpRequests] + [HandleHelpRequest] + [HandlePlanRequest] [HandleShellCompletion] [HandleVisualStudioDebugging] public abstract partial class NukeBuild @@ -87,7 +88,7 @@ protected static int Execute(Expression> defaultTargetExpress internal void ExecuteExtensions() where T : IBuildExtension { - GetType().GetCustomAttributes().OfType().ForEach(x => x.Execute(this, ExecutableTargets)); + GetType().GetCustomAttributes().OfType().ForEach(x => x.Execute(this, ExecutableTargets, ExecutionPlan)); } protected internal virtual IOutputSink OutputSink diff --git a/source/Nuke.Common/Tooling/VerbosityMappingAttribute.cs b/source/Nuke.Common/Tooling/VerbosityMappingAttribute.cs index 5cf9a7457..274adc10a 100644 --- a/source/Nuke.Common/Tooling/VerbosityMappingAttribute.cs +++ b/source/Nuke.Common/Tooling/VerbosityMappingAttribute.cs @@ -25,7 +25,10 @@ public VerbosityMappingAttribute(Type targetType) public string Normal { get; set; } public string Verbose { get; set; } - public void Execute(NukeBuild build, IReadOnlyCollection executableTargets) + public void Execute( + NukeBuild build, + IReadOnlyCollection executableTargets, + IReadOnlyCollection executionPlan) { object GetMappedValue(string name) => _targetType