diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/MSBuild/DotNetCoreMSBuildBuilderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/MSBuild/DotNetCoreMSBuildBuilderFixture.cs index 77ca55edda..7b38b4a55c 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/MSBuild/DotNetCoreMSBuildBuilderFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/MSBuild/DotNetCoreMSBuildBuilderFixture.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Collections.Generic; using Cake.Common.Tools.DotNet.MSBuild; namespace Cake.Common.Tests.Fixtures.Tools.DotNet.MSBuild @@ -9,11 +11,12 @@ namespace Cake.Common.Tests.Fixtures.Tools.DotNet.MSBuild internal sealed class DotNetMSBuildBuilderFixture : DotNetFixture { public string Project { get; set; } + public Action> StandardOutputAction { get; set; } protected override void RunTool() { var tool = new DotNetMSBuildBuilder(FileSystem, Environment, ProcessRunner, Tools); - tool.Build(Project, Settings); + tool.Build(Project, Settings, StandardOutputAction); } } } \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs index a5ee20407c..0b3d30b2b6 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using Cake.Common.Tools.MSBuild; using Cake.Core; @@ -15,6 +16,7 @@ internal sealed class MSBuildRunnerFixture : ToolFixture { public HashSet KnownMSBuildPaths { get; } public FilePath Solution { get; set; } + public Action> StandardOutputAction { get; set; } public MSBuildRunnerFixture(bool is64BitOperativeSystem, PlatformFamily platformFamily) : base("MSBuild.exe") @@ -83,7 +85,7 @@ public void GivenMSBuildIsNotInstalled() protected override void RunTool() { var runner = new MSBuildRunner(FileSystem, Environment, ProcessRunner, Tools); - runner.Run(Solution, Settings); + runner.Run(Solution, Settings, StandardOutputAction); } } } \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildBuilderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildBuilderTests.cs index 541036be8f..02d0ae43de 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildBuilderTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildBuilderTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tests.Fixtures.Tools.DotNet.MSBuild; using Cake.Common.Tools.DotNet; @@ -205,6 +206,113 @@ public void Should_Throw_If_Property_Has_No_Value(string[] propertyValues) AssertEx.IsArgumentException(result, "Properties", "A property must have at least one non-empty value"); } + [Fact] + public void Should_Append_GetProperty_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.WithGetProperty("A"); + fixture.Settings.WithGetProperty("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"Properties\": {", + " \"A\": \"A value\",", + " \"B\": \"B value\"", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /getProperty:A /getProperty:B", + result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Fact] + public void Should_Append_GetItem_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.WithGetItem("A"); + fixture.Settings.WithGetItem("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"Items\": {", + " \"A\": [", + " {", + " \"Identity\": \"Identity value\"", + " }", + " ],", + " \"B\": [", + " {", + " \"Identity\": \"Identity value\"", + " }", + " ],", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /getItem:A /getItem:B", + result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Fact] + public void Should_Append_GetTargetResult_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.WithGetTargetResult("A"); + fixture.Settings.WithGetTargetResult("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"TargetResults\": {", + " \"A\": {", + " \"Result\": \"Success\"", + " \"Items\": []", + " },", + " \"B\": {", + " \"Result\": \"Success\"", + " \"Items\": []", + " }", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /getTargetResult:A /getTargetResult:B", + result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + [Theory] [InlineData(0)] [InlineData(-10)] diff --git a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs index 87432bfd71..4d63f2dde0 100644 --- a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.MSBuild; using Cake.Core; @@ -1014,6 +1015,113 @@ public void Should_Not_Escape_Semicolons_For_Specified_Property_Arguments_When_A "\"C:/Working/src/Solution.sln\"", result.Args); } + [Fact] + public void Should_Append_GetProperty_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithGetProperty("A"); + fixture.Settings.WithGetProperty("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"Properties\": {", + " \"A\": \"A value\",", + " \"B\": \"B value\"", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /getProperty:A /getProperty:B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Fact] + public void Should_Append_GetItem_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithGetItem("A"); + fixture.Settings.WithGetItem("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"Items\": {", + " \"A\": [", + " {", + " \"Identity\": \"Identity value\"", + " }", + " ],", + " \"B\": [", + " {", + " \"Identity\": \"Identity value\"", + " }", + " ],", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /getItem:A /getItem:B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Fact] + public void Should_Append_GetTargetResult_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithGetTargetResult("A"); + fixture.Settings.WithGetTargetResult("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"TargetResults\": {", + " \"A\": {", + " \"Result\": \"Success\"", + " \"Items\": []", + " },", + " \"B\": {", + " \"Result\": \"Success\"", + " \"Items\": []", + " }", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /getTargetResult:A /getTargetResult:B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + [Theory] [InlineData("Release", "/v:normal /p:Configuration=\"Release\" /target:Build \"C:/Working/src/Solution.sln\"")] [InlineData("Custom Spaced", "/v:normal /p:Configuration=\"Custom Spaced\" /target:Build \"C:/Working/src/Solution.sln\"")] diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs index c84101cd00..9a0bec8800 100644 --- a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs @@ -1210,7 +1210,7 @@ public static void DotNetRun(this ICakeContext context, string project, DotNetRu [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] public static void DotNetMSBuild(this ICakeContext context) { - context.DotNetMSBuild(null, null); + context.DotNetMSBuild((string)null, (DotNetMSBuildSettings)null); } /// @@ -1263,6 +1263,32 @@ public static void DotNetMSBuild(this ICakeContext context, DotNetMSBuildSetting context.DotNetMSBuild(null, settings); } + /// + /// Builds the specified targets in a project file found in the current working directory. + /// + /// The context. + /// The settings. + /// The action to invoke with the standard output. + /// + /// + /// var settings = new DotNetMSBuildSettings + /// { + /// NoLogo = true, + /// MaxCpuCount = -1 + /// }; + /// + /// DotNetMSBuild(settings, + /// output => foreach(var line in output) outputBuilder.AppendLine(line)); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("MSBuild")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] + public static void DotNetMSBuild(this ICakeContext context, DotNetMSBuildSettings settings, Action> standardOutputAction) + { + context.DotNetMSBuild(null, settings, standardOutputAction); + } + /// /// Builds the specified targets in the project file. /// @@ -1300,7 +1326,49 @@ public static void DotNetMSBuild(this ICakeContext context, string projectOrDire } var builder = new DotNetMSBuildBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - builder.Build(projectOrDirectory, settings); + builder.Build(projectOrDirectory, settings, null); + } + + /// + /// Builds the specified targets in the project file. + /// + /// The context. + /// Project file or directory to search for project file. + /// The settings. + /// The action to invoke with the standard output. + /// + /// + /// var settings = new DotNetMSBuildSettings + /// { + /// NoLogo = true, + /// MaxCpuCount = -1 + /// }; + /// + /// DotNetMSBuild("foobar.proj", settings, + /// output => foreach(var line in output) outputBuilder.AppendLine(line)); + /// + /// + /// + /// If a project file is not specified, MSBuild searches the current working directory for a file that has a file + /// extension that ends in "proj" and uses that file. If a directory is specified, MSBuild searches that directory for a project file. + /// + [CakeMethodAlias] + [CakeAliasCategory("MSBuild")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] + public static void DotNetMSBuild(this ICakeContext context, string projectOrDirectory, DotNetMSBuildSettings settings, Action> standardOutputAction) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (settings is null) + { + settings = new DotNetMSBuildSettings(); + } + + var builder = new DotNetMSBuildBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + builder.Build(projectOrDirectory, settings, standardOutputAction); } /// diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildBuilder.cs b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildBuilder.cs index a9b983a8a1..8b019cf693 100644 --- a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildBuilder.cs +++ b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildBuilder.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -37,14 +38,19 @@ public DotNetMSBuildBuilder( /// /// The target project path. /// The settings. - public void Build(string projectOrDirectory, DotNetMSBuildSettings settings) + /// The action to invoke with the standard output. + public void Build(string projectOrDirectory, DotNetMSBuildSettings settings, Action> standardOutputAction) { if (settings == null) { throw new ArgumentNullException(nameof(settings)); } - RunCommand(settings, GetArguments(projectOrDirectory, settings)); + RunCommand( + settings, + GetArguments(projectOrDirectory, settings), + standardOutputAction == null ? null : new ProcessSettings { RedirectStandardOutput = true }, + standardOutputAction == null ? null : new Action(process => standardOutputAction(process.GetStandardOutput()))); } private ProcessArgumentBuilder GetArguments(string projectOrDirectory, DotNetMSBuildSettings settings) diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettings.cs b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettings.cs index da992f5a46..d6cfefcca7 100644 --- a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettings.cs +++ b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettings.cs @@ -135,6 +135,27 @@ public string PackageReleaseNotes /// public ICollection Targets { get; } + /// + /// Gets the properties to retrieve. + /// + /// The properties to retrieve. + /// For more information, refer to Evaluate items and properties and display results of targets. + public HashSet GetProperties { get; } + + /// + /// Gets the items to retrieve. + /// + /// The items to retrieve. + /// For more information, refer to Evaluate items and items and display results of targets. + public HashSet GetItems { get; } + + /// + /// Gets the target results to retrieve. + /// + /// The target results to retrieve. + /// For more information, refer to Evaluate items and items and display results of targets. + public HashSet GetTargetResults { get; } + /// /// Gets or sets the version of the Toolset to use to build the project. /// @@ -228,6 +249,9 @@ public DotNetMSBuildSettings() { Properties = new Dictionary>(StringComparer.OrdinalIgnoreCase); Targets = new List(); + GetProperties = new HashSet(StringComparer.OrdinalIgnoreCase); + GetItems = new HashSet(StringComparer.OrdinalIgnoreCase); + GetTargetResults = new HashSet(StringComparer.OrdinalIgnoreCase); ResponseFiles = new List(); DistributedLoggers = new List(); FileLoggers = new List(); @@ -248,4 +272,4 @@ private string GetPropertyValueOrDefault(string propertyName, string @default = return propertyValue; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensions.cs b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensions.cs index 1194d0509f..4bde4220f8 100644 --- a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensions.cs +++ b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensions.cs @@ -57,6 +57,84 @@ public static DotNetMSBuildSettings WithProperty(this DotNetMSBuildSettings sett return settings; } + /// + /// Adds a property to retrieve the value. + /// + /// The settings. + /// The name of the property to retrieve the value. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithGetProperty(this DotNetMSBuildSettings settings, string name) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetProperties.Add(name); + + return settings; + } + + /// + /// Adds a item to retrieve the value. + /// + /// The settings. + /// The name of the item to retrieve the value. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithGetItem(this DotNetMSBuildSettings settings, string name) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetItems.Add(name); + + return settings; + } + + /// + /// Adds a target to retrieve the result. + /// + /// The settings. + /// The name of the target to retrieve the result. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithGetTargetResult(this DotNetMSBuildSettings settings, string name) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetTargetResults.Add(name); + + return settings; + } + /// /// Shows detailed information at the end of the build log about the configurations that were built and how they were scheduled to nodes. /// diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildArgumentBuilderExtensions.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildArgumentBuilderExtensions.cs index 46ea94d326..608573867c 100644 --- a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildArgumentBuilderExtensions.cs +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildArgumentBuilderExtensions.cs @@ -63,6 +63,39 @@ public static void AppendMSBuildSettings(this ProcessArgumentBuilder builder, Do msBuilder.AppendMSBuildSwitch("property", $"{property.Key}={property.BuildMSBuildPropertyParameterString()}"); } + // Got any properties to retrieve? + foreach (var property in settings.GetProperties) + { + if (property == null || string.IsNullOrWhiteSpace(property)) + { + throw new ArgumentException("A property to retrieve must have have non-empty name", nameof(settings.Properties)); + } + + msBuilder.AppendMSBuildSwitch("getProperty", property); + } + + // Got any items to retrieve? + foreach (var item in settings.GetItems) + { + if (item == null || string.IsNullOrWhiteSpace(item)) + { + throw new ArgumentException("An item to retrieve must have have non-empty name", nameof(settings.Properties)); + } + + msBuilder.AppendMSBuildSwitch("getItem", item); + } + + // Got any target results to retrieve? + foreach (var target in settings.GetTargetResults) + { + if (target == null || string.IsNullOrWhiteSpace(target)) + { + throw new ArgumentException("An target to retrieve results must have have non-empty name", nameof(settings.Properties)); + } + + msBuilder.AppendMSBuildSwitch("getTargetResult", target); + } + // Set the maximum number of processors? if (settings.MaxCpuCount.HasValue) { diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildAliases.cs b/src/Cake.Common/Tools/MSBuild/MSBuildAliases.cs index 23bd9b7a44..600396885e 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildAliases.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildAliases.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using Cake.Core; using Cake.Core.Annotations; using Cake.Core.IO; @@ -32,7 +33,25 @@ public static class MSBuildAliases [CakeMethodAlias] public static void MSBuild(this ICakeContext context, FilePath solution) { - MSBuild(context, solution, settings => { }); + MSBuild(context, solution, (MSBuildSettings settings) => { }); + } + + /// + /// Builds the specified solution or MsBuild project file using MSBuild. + /// + /// The context. + /// The solution or MsBuild project file to build. + /// The action to invoke with the standard output. + /// + /// + /// MSBuild("./src/Cake.sln", + /// output => foreach(var line in output) outputBuilder.AppendLine(line)); + /// + /// + [CakeMethodAlias] + public static void MSBuild(this ICakeContext context, FilePath solution, Action> standardOutputAction) + { + MSBuild(context, solution, settings => { }, standardOutputAction); } /// @@ -75,6 +94,48 @@ public static void MSBuild(this ICakeContext context, FilePath solution, Action< /// /// The context. /// The solution or MsBuild project file to build. + /// The settings configurator. + /// The action to invoke with the standard output. + /// + /// + /// var outputBuilder = new StringBuilder(); + /// MSBuild("./src/Cake.sln", configurator => + /// configurator.SetConfiguration("Debug") + /// .SetVerbosity(Verbosity.Minimal) + /// .UseToolVersion(MSBuildToolVersion.VS2015) + /// .SetMSBuildPlatform(MSBuildPlatform.x86) + /// .SetPlatformTarget(PlatformTarget.MSIL), + /// output => foreach(var line in output) outputBuilder.AppendLine(line)); + /// + /// + [CakeMethodAlias] + public static void MSBuild(this ICakeContext context, FilePath solution, Action configurator, Action> standardOutputAction) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (configurator == null) + { + throw new ArgumentNullException(nameof(configurator)); + } + if (standardOutputAction == null) + { + throw new ArgumentNullException(nameof(standardOutputAction)); + } + + var settings = new MSBuildSettings(); + configurator(settings); + + // Perform the build. + MSBuild(context, solution, settings, standardOutputAction); + } + + /// + /// Builds the specified solution using MSBuild. + /// + /// The context. + /// The solution or MsBuild project file to build. /// The settings. /// /// @@ -99,7 +160,46 @@ public static void MSBuild(this ICakeContext context, FilePath solution, MSBuild } var runner = new MSBuildRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - runner.Run(solution, settings); + runner.Run(solution, settings, null); + } + + /// + /// Builds the specified solution or MsBuild project file using MSBuild. + /// + /// The context. + /// The solution to build. + /// The settings. + /// The action to invoke with the standard output. + /// + /// + /// var outputBuilder = new StringBuilder(); + /// MSBuild("./src/Cake.sln", new MSBuildSettings { + /// Verbosity = Verbosity.Minimal, + /// ToolVersion = MSBuildToolVersion.VS2015, + /// Configuration = "Release", + /// PlatformTarget = PlatformTarget.MSIL + /// }, + /// output => foreach(var line in output) outputBuilder.AppendLine(line)); + /// + /// + [CakeMethodAlias] + public static void MSBuild(this ICakeContext context, FilePath solution, MSBuildSettings settings, Action> standardOutputAction) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (standardOutputAction == null) + { + throw new ArgumentNullException(nameof(standardOutputAction)); + } + + var runner = new MSBuildRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(solution, settings, standardOutputAction); } } } \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs b/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs index b6f04f21fc..2955e88b34 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs @@ -43,9 +43,14 @@ public MSBuildRunner( /// /// The solution or MsBuild project file to build. /// The settings. - public void Run(FilePath solution, MSBuildSettings settings) + /// The action to invoke with the standard output. + public void Run(FilePath solution, MSBuildSettings settings, Action> standardOutputAction) { - Run(settings, GetArguments(solution, settings)); + Run( + settings, + GetArguments(solution, settings), + standardOutputAction == null ? null : new ProcessSettings { RedirectStandardOutput = true }, + standardOutputAction == null ? null : new Action(process => standardOutputAction(process.GetStandardOutput()))); } private ProcessArgumentBuilder GetArguments(FilePath projectFile, MSBuildSettings settings) @@ -143,6 +148,33 @@ private ProcessArgumentBuilder GetArguments(FilePath projectFile, MSBuildSetting } } + // Got any properties to retrieve? + if (settings.GetProperties.Count > 0) + { + foreach (var property in GetGetPropertiesArguments(settings.GetProperties)) + { + builder.Append(property); + } + } + + // Got any items to retrieve? + if (settings.GetItems.Count > 0) + { + foreach (var property in GetGetItemsArguments(settings.GetItems)) + { + builder.Append(property); + } + } + + // Got any target results to retrieve? + if (settings.GetTargetResults.Count > 0) + { + foreach (var property in GetGetTargetResultsArguments(settings.GetTargetResults)) + { + builder.Append(property); + } + } + if (settings.Loggers.Count > 0) { foreach (var logger in settings.Loggers) @@ -301,6 +333,30 @@ private static IEnumerable GetPropertyArguments(IDictionary GetGetPropertiesArguments(HashSet getProperties) + { + foreach (var getProperty in getProperties) + { + yield return string.Concat("/getProperty:", getProperty); + } + } + + private static IEnumerable GetGetItemsArguments(HashSet getItems) + { + foreach (var getItem in getItems) + { + yield return string.Concat("/getItem:", getItem); + } + } + + private static IEnumerable GetGetTargetResultsArguments(HashSet getTargetResults) + { + foreach (var getTargetResult in getTargetResults) + { + yield return string.Concat("/getTargetResult:", getTargetResult); + } + } + /// /// Gets the name of the tool. /// diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs b/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs index 11c56e61b6..7aec6e44b9 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs @@ -17,6 +17,9 @@ public sealed class MSBuildSettings : ToolSettings { private readonly HashSet _targets; private readonly Dictionary> _properties; + private readonly HashSet _getProperties; + private readonly HashSet _getItems; + private readonly HashSet _getTargetResults; private readonly List _loggers; private readonly List _fileLoggers; private readonly HashSet _warningsAsErrorCodes; @@ -36,6 +39,27 @@ public sealed class MSBuildSettings : ToolSettings /// The properties. public IDictionary> Properties => _properties; + /// + /// Gets the properties to retrieve. + /// + /// The properties to retrieve. + /// For more information, refer to Evaluate items and properties and display results of targets. + public HashSet GetProperties => _getProperties; + + /// + /// Gets the items to retrieve. + /// + /// The items to retrieve. + /// For more information, refer to Evaluate items and items and display results of targets. + public HashSet GetItems => _getItems; + + /// + /// Gets the target results to retrieve. + /// + /// The target results to retrieve. + /// For more information, refer to Evaluate items and items and display results of targets. + public HashSet GetTargetResults => _getTargetResults; + /// /// Gets or sets the platform target. /// @@ -295,6 +319,9 @@ public MSBuildSettings() { _targets = new HashSet(StringComparer.OrdinalIgnoreCase); _properties = new Dictionary>(StringComparer.OrdinalIgnoreCase); + _getProperties = new HashSet(StringComparer.OrdinalIgnoreCase); + _getItems = new HashSet(StringComparer.OrdinalIgnoreCase); + _getTargetResults = new HashSet(StringComparer.OrdinalIgnoreCase); _loggers = new List(); _fileLoggers = new List(); _warningsAsErrorCodes = new HashSet(StringComparer.OrdinalIgnoreCase); diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs b/src/Cake.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs index 9b63667a16..a9ec1c0de2 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs @@ -201,6 +201,84 @@ public static MSBuildSettings WithProperty(this MSBuildSettings settings, string return settings; } + /// + /// Adds a property to retrieve the value. + /// + /// The settings. + /// The name of the property to retrieve the value. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithGetProperty(this MSBuildSettings settings, string name) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetProperties.Add(name); + + return settings; + } + + /// + /// Adds a item to retrieve the value. + /// + /// The settings. + /// The name of the item to retrieve the value. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithGetItem(this MSBuildSettings settings, string name) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetItems.Add(name); + + return settings; + } + + /// + /// Adds a target to retrieve the result. + /// + /// The settings. + /// The name of the target to retrieve the result. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithGetTargetResult(this MSBuildSettings settings, string name) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetTargetResults.Add(name); + + return settings; + } + /// /// Sets the configuration. ///