diff --git a/src/Cake.Common/ProcessAliases.cs b/src/Cake.Common/ProcessAliases.cs index a6b0981767..9d8d6193ad 100644 --- a/src/Cake.Common/ProcessAliases.cs +++ b/src/Cake.Common/ProcessAliases.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Globalization; using Cake.Core; using Cake.Core.Annotations; using Cake.Core.IO; @@ -46,6 +48,37 @@ public static int StartProcess(this ICakeContext context, FilePath fileName) /// [CakeMethodAlias] public static int StartProcess(this ICakeContext context, FilePath fileName, ProcessSettings settings) + { + IEnumerable redirectedOutput; + return StartProcess(context, fileName, settings, out redirectedOutput); + } + + /// + /// Starts the process resource that is specified by the filename and settings. + /// + /// The context. + /// Name of the file. + /// The settings. + /// outputs process output RedirectStandardOutput is true + /// The exit code that the started process specified when it terminated. + /// + /// + /// IEnumerable<string> redirectedOutput; + /// var exitCodeWithArgument = StartProcess("ping", new ProcessSettings{ + /// Arguments = "localhost", + /// RedirectStandardOutput = true + /// }, + /// out redirectedOutput + /// ); + /// //Output last line of process output + /// Information("Last line of output: {0}", redirectedOutput.LastOrDefault()); + /// + /// // This should output 0 as valid arguments supplied + /// Information("Exit code: {0}", exitCodeWithArgument); + /// + /// + [CakeMethodAlias] + public static int StartProcess(this ICakeContext context, FilePath fileName, ProcessSettings settings, out IEnumerable redirectedOutput) { if (context == null) { @@ -72,8 +105,27 @@ public static int StartProcess(this ICakeContext context, FilePath fileName, Pro } // Wait for the process to stop. - process.WaitForExit(); + if (settings.Timeout.HasValue) + { + if (!process.WaitForExit(settings.Timeout.Value)) + { + throw new TimeoutException( + string.Format( + CultureInfo.InvariantCulture, + "Process TimeOut ({0}): {1}", + settings.Timeout.Value, + fileName)); + } + } + else + { + process.WaitForExit(); + } + redirectedOutput = settings.RedirectStandardOutput + ? process.GetStandardOutput() + : null; + // Return the exit code. return process.GetExitCode(); } diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/MethodAliasGeneratorTests.cs b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/MethodAliasGeneratorTests.cs index 1ce4f42a41..3c2f4f23cb 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/MethodAliasGeneratorTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/MethodAliasGeneratorTests.cs @@ -85,7 +85,7 @@ public void Should_Return_Correctly_Generated_Wrapper_For_Non_Generic_Type_With_ { const string expected = "public void NonGeneric_ExtensionMethodWithParameter(System.Int32 value){" + "Cake.Core.Tests.Fixtures.MethodAliasGeneratorFixture.NonGeneric_ExtensionMethodWithParameter" + - "(GetContext(),value);}"; + "(GetContext(), value);}"; var method = typeof(MethodAliasGeneratorFixture).GetMethod("NonGeneric_ExtensionMethodWithParameter"); @@ -101,7 +101,7 @@ public void Should_Return_Correctly_Generated_Wrapper_For_Non_Generic_Type_With_ { const string expected = "public void NonGeneric_ExtensionMethodWithGenericParameter(System.Action value){" + "Cake.Core.Tests.Fixtures.MethodAliasGeneratorFixture.NonGeneric_ExtensionMethodWithGenericParameter" + - "(GetContext(),value);}"; + "(GetContext(), value);}"; var method = typeof(MethodAliasGeneratorFixture).GetMethod("NonGeneric_ExtensionMethodWithGenericParameter"); @@ -149,7 +149,7 @@ public void Should_Return_Correctly_Generated_Wrapper_For_Generic_Type_With_Argu { const string expected = "public void Generic_ExtensionMethodWithParameter(TTest value){" + "Cake.Core.Tests.Fixtures.MethodAliasGeneratorFixture.Generic_ExtensionMethodWithParameter" + - "(GetContext(),value);}"; + "(GetContext(), value);}"; var method = typeof(MethodAliasGeneratorFixture).GetMethods().SingleOrDefault(x => x.Name == "Generic_ExtensionMethodWithParameter"); @@ -165,7 +165,7 @@ public void Should_Return_Correctly_Generated_Wrapper_For_Generic_Type_With_Gene { const string expected = "public TTest Generic_ExtensionMethodWithGenericReturnValue(TTest value){" + "return Cake.Core.Tests.Fixtures.MethodAliasGeneratorFixture.Generic_ExtensionMethodWithGenericReturnValue" + - "(GetContext(),value);}"; + "(GetContext(), value);}"; var method = typeof(MethodAliasGeneratorFixture).GetMethods().SingleOrDefault(x => x.Name == "Generic_ExtensionMethodWithGenericReturnValue"); @@ -181,7 +181,7 @@ public void Should_Return_Correctly_Generated_Wrapper_For_Non_Generic_Type_With_ { const string expected = "public void NonGeneric_ExtensionMethodWithParameterArray(params System.Int32[] values){" + "Cake.Core.Tests.Fixtures.MethodAliasGeneratorFixture.NonGeneric_ExtensionMethodWithParameterArray" + - "(GetContext(),values);}"; + "(GetContext(), values);}"; var method = typeof(MethodAliasGeneratorFixture).GetMethod("NonGeneric_ExtensionMethodWithParameterArray"); diff --git a/src/Cake.Core/Extensions/TypeExtensions.cs b/src/Cake.Core/Extensions/TypeExtensions.cs index e846bbf533..c1c775c48d 100644 --- a/src/Cake.Core/Extensions/TypeExtensions.cs +++ b/src/Cake.Core/Extensions/TypeExtensions.cs @@ -40,8 +40,9 @@ public static string GetFullName(this Type type, bool includeNamespace = true) { return type.Name; } - return type.IsGenericType - ? GetGenericTypeName(type, includeNamespace) + Type genericType; + return type.IsGenericType(out genericType) + ? GetGenericTypeName(genericType, includeNamespace) : includeNamespace ? type.FullName : type.Name; } @@ -60,6 +61,14 @@ private static string GetGenericTypeName(this Type type, bool includeNamespace) return builder.ToString(); } + private static bool IsGenericType(this Type type, out Type genericType) + { + genericType = type.IsByRef + ? (type.GetElementType() ?? type) + : type; + return genericType.IsGenericType; + } + private static string GetGenericTypeArguments(this Type type, bool includeNamespace) { var genericArguments = new List(); diff --git a/src/Cake.Core/IO/IProcess.cs b/src/Cake.Core/IO/IProcess.cs index 3285967751..36cae81541 100644 --- a/src/Cake.Core/IO/IProcess.cs +++ b/src/Cake.Core/IO/IProcess.cs @@ -12,6 +12,13 @@ public interface IProcess /// void WaitForExit(); + /// + /// Waits for the process to exit with possible timeout for command. + /// + /// The amount of time, in milliseconds, to wait for the associated process to exit. The maximum is the largest possible value of a 32-bit integer, which represents infinity to the operating system. + /// true if the associated process has exited; otherwise, false. + bool WaitForExit(int milliseconds); + /// /// Gets the exit code of the process. /// diff --git a/src/Cake.Core/IO/ProcessSettings.cs b/src/Cake.Core/IO/ProcessSettings.cs index 5e7450a6d2..7c851615ce 100644 --- a/src/Cake.Core/IO/ProcessSettings.cs +++ b/src/Cake.Core/IO/ProcessSettings.cs @@ -22,5 +22,10 @@ public sealed class ProcessSettings /// /// true if output should be written to ; otherwise, false. The default is false. public bool RedirectStandardOutput { get; set; } + + /// + /// Gets or sets optional timeout for process execution + /// + public int? Timeout { get; set; } } } diff --git a/src/Cake.Core/IO/ProcessWrapper.cs b/src/Cake.Core/IO/ProcessWrapper.cs index 6e6f1941a7..9cfc25cece 100644 --- a/src/Cake.Core/IO/ProcessWrapper.cs +++ b/src/Cake.Core/IO/ProcessWrapper.cs @@ -20,6 +20,20 @@ public void WaitForExit() _process.WaitForExit(); } + public bool WaitForExit(int milliseconds) + { + if (_process.WaitForExit(milliseconds)) + { + return true; + } + _process.Refresh(); + if (!_process.HasExited) + { + _process.Kill(); + } + return false; + } + public int GetExitCode() { return _process.ExitCode; diff --git a/src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs b/src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs index 5625abe33d..edee643c8d 100644 --- a/src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs +++ b/src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs @@ -66,7 +66,7 @@ private static string GenerateCode(MethodInfo method) } builder.Append("("); - builder.Append(GetProxyParameters(parameters)); + builder.Append(string.Concat(GetProxyParameters(parameters, true))); builder.Append(")"); builder.Append("{"); @@ -86,7 +86,7 @@ private static string GenerateCode(MethodInfo method) } builder.Append("("); - builder.Append(GetCallArguments(parameters)); + builder.Append(string.Concat(GetProxyParameters(parameters, false))); builder.Append(");"); // End method. @@ -102,24 +102,42 @@ private static string GetReturnType(MethodInfo method) : method.ReturnType.GetFullName(); } - private static string GetProxyParameters(IEnumerable parameters) + private static IEnumerable GetProxyParameters(IEnumerable parameters, bool includeType) { - var result = new List(); + var first = includeType; + if (!includeType) + { + yield return "GetContext()"; + } foreach (var parameter in parameters) { - var isParameterArray = parameter.IsDefined(typeof(ParamArrayAttribute)); - var typeName = parameter.ParameterType.GetFullName(); - var typeDeclaration = isParameterArray ? string.Concat("params ", typeName) : typeName; - result.Add(string.Concat(typeDeclaration, " ", parameter.Name)); + if (first) + { + first = false; + } + else + { + yield return ", "; + } + if (parameter.IsOut) + { + yield return "out "; + } + else if (parameter.ParameterType.IsByRef) + { + yield return "ref "; + } + if (includeType) + { + if (parameter.IsDefined(typeof(ParamArrayAttribute))) + { + yield return "params "; + } + yield return parameter.ParameterType.GetFullName(); + yield return " "; + } + yield return parameter.Name; } - return string.Join(",", result); - } - - private static string GetCallArguments(IEnumerable parameters) - { - var result = new List { "GetContext()" }; - result.AddRange(parameters.Select(x => x.Name)); - return string.Join(",", result); } private static void BuildGenericArguments(MethodInfo method, StringBuilder builder)