Skip to content

Commit 6a26f31

Browse files
paulomorgadodevlead
authored andcommitted
Add support for capturing MSBuild properties, items and target results
Introduce the ability to capture and process the standard output of MSBuild commands in the Cake build automation system. - Added `StandardOutputAction` properties to `DotNetCoreMSBuildBuilderFixture` and `MSBuildRunnerFixture`. - Updated `RunTool` methods to pass `StandardOutputAction` to `Build` and `Run` methods. - Added new test cases in `DotNetMSBuildBuilderTests` and `MSBuildRunnerTests` to verify functionality. - Added overloads in `DotNetAliases` and `MSBuildAliases` to accept `standardOutputAction` parameter. - Updated `DotNetMSBuildBuilder` and `MSBuildRunner` to handle `standardOutputAction` and configure process settings. - Enhanced `MSBuildArgumentBuilderExtensions` to append MSBuild arguments for properties, items, and target results. - Extended `DotNetMSBuildSettings` and `MSBuildSettings` with new properties and collections. - Added extension methods in `DotNetMSBuildSettingsExtensions` for fluent configuration.
1 parent db1abc6 commit 6a26f31

File tree

13 files changed

+703
-11
lines changed

13 files changed

+703
-11
lines changed

src/Cake.Common.Tests/Fixtures/Tools/DotNet/MSBuild/DotNetCoreMSBuildBuilderFixture.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
6+
using System.Collections.Generic;
57
using Cake.Common.Tools.DotNet.MSBuild;
68

79
namespace Cake.Common.Tests.Fixtures.Tools.DotNet.MSBuild
810
{
911
internal sealed class DotNetMSBuildBuilderFixture : DotNetFixture<DotNetMSBuildSettings>
1012
{
1113
public string Project { get; set; }
14+
public Action<IEnumerable<string>> StandardOutputAction { get; set; }
1215

1316
protected override void RunTool()
1417
{
1518
var tool = new DotNetMSBuildBuilder(FileSystem, Environment, ProcessRunner, Tools);
16-
tool.Build(Project, Settings);
19+
tool.Build(Project, Settings, StandardOutputAction);
1720
}
1821
}
1922
}

src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Generic;
67
using Cake.Common.Tools.MSBuild;
78
using Cake.Core;
@@ -15,6 +16,7 @@ internal sealed class MSBuildRunnerFixture : ToolFixture<MSBuildSettings>
1516
{
1617
public HashSet<FilePath> KnownMSBuildPaths { get; }
1718
public FilePath Solution { get; set; }
19+
public Action<IEnumerable<string>> StandardOutputAction { get; set; }
1820

1921
public MSBuildRunnerFixture(bool is64BitOperativeSystem, PlatformFamily platformFamily)
2022
: base("MSBuild.exe")
@@ -83,7 +85,7 @@ public void GivenMSBuildIsNotInstalled()
8385
protected override void RunTool()
8486
{
8587
var runner = new MSBuildRunner(FileSystem, Environment, ProcessRunner, Tools);
86-
runner.Run(Solution, Settings);
88+
runner.Run(Solution, Settings, StandardOutputAction);
8789
}
8890
}
8991
}

src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildBuilderTests.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Collections.Generic;
67
using Cake.Common.Tests.Fixtures.Tools;
78
using Cake.Common.Tests.Fixtures.Tools.DotNet.MSBuild;
89
using Cake.Common.Tools.DotNet;
@@ -205,6 +206,113 @@ public void Should_Throw_If_Property_Has_No_Value(string[] propertyValues)
205206
AssertEx.IsArgumentException(result, "Properties", "A property must have at least one non-empty value");
206207
}
207208

209+
[Fact]
210+
public void Should_Append_GetProperty_To_Process_Arguments_And_Collects_Output()
211+
{
212+
IEnumerable<string> msbuildOutput = null;
213+
214+
// Given
215+
var fixture = new DotNetMSBuildBuilderFixture();
216+
fixture.Settings.WithGetProperty("A");
217+
fixture.Settings.WithGetProperty("B");
218+
fixture.StandardOutputAction = lines => msbuildOutput = lines;
219+
var standardOutput = new string[]
220+
{
221+
"{",
222+
" \"Properties\": {",
223+
" \"A\": \"A value\",",
224+
" \"B\": \"B value\"",
225+
" }",
226+
"}",
227+
};
228+
fixture.ProcessRunner.Process.SetStandardOutput(standardOutput);
229+
230+
// When
231+
var result = fixture.Run();
232+
233+
// Then
234+
Assert.Equal("msbuild /getProperty:A /getProperty:B",
235+
result.Args);
236+
237+
Assert.Equal(standardOutput, msbuildOutput);
238+
}
239+
240+
[Fact]
241+
public void Should_Append_GetItem_To_Process_Arguments_And_Collects_Output()
242+
{
243+
IEnumerable<string> msbuildOutput = null;
244+
245+
// Given
246+
var fixture = new DotNetMSBuildBuilderFixture();
247+
fixture.Settings.WithGetItem("A");
248+
fixture.Settings.WithGetItem("B");
249+
fixture.StandardOutputAction = lines => msbuildOutput = lines;
250+
var standardOutput = new string[]
251+
{
252+
"{",
253+
" \"Items\": {",
254+
" \"A\": [",
255+
" {",
256+
" \"Identity\": \"Identity value\"",
257+
" }",
258+
" ],",
259+
" \"B\": [",
260+
" {",
261+
" \"Identity\": \"Identity value\"",
262+
" }",
263+
" ],",
264+
" }",
265+
"}",
266+
};
267+
fixture.ProcessRunner.Process.SetStandardOutput(standardOutput);
268+
269+
// When
270+
var result = fixture.Run();
271+
272+
// Then
273+
Assert.Equal("msbuild /getItem:A /getItem:B",
274+
result.Args);
275+
276+
Assert.Equal(standardOutput, msbuildOutput);
277+
}
278+
279+
[Fact]
280+
public void Should_Append_GetTargetResult_To_Process_Arguments_And_Collects_Output()
281+
{
282+
IEnumerable<string> msbuildOutput = null;
283+
284+
// Given
285+
var fixture = new DotNetMSBuildBuilderFixture();
286+
fixture.Settings.WithGetTargetResult("A");
287+
fixture.Settings.WithGetTargetResult("B");
288+
fixture.StandardOutputAction = lines => msbuildOutput = lines;
289+
var standardOutput = new string[]
290+
{
291+
"{",
292+
" \"TargetResults\": {",
293+
" \"A\": {",
294+
" \"Result\": \"Success\"",
295+
" \"Items\": []",
296+
" },",
297+
" \"B\": {",
298+
" \"Result\": \"Success\"",
299+
" \"Items\": []",
300+
" }",
301+
" }",
302+
"}",
303+
};
304+
fixture.ProcessRunner.Process.SetStandardOutput(standardOutput);
305+
306+
// When
307+
var result = fixture.Run();
308+
309+
// Then
310+
Assert.Equal("msbuild /getTargetResult:A /getTargetResult:B",
311+
result.Args);
312+
313+
Assert.Equal(standardOutput, msbuildOutput);
314+
}
315+
208316
[Theory]
209317
[InlineData(0)]
210318
[InlineData(-10)]

src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Collections.Generic;
56
using Cake.Common.Tests.Fixtures.Tools;
67
using Cake.Common.Tools.MSBuild;
78
using Cake.Core;
@@ -1014,6 +1015,113 @@ public void Should_Not_Escape_Semicolons_For_Specified_Property_Arguments_When_A
10141015
"\"C:/Working/src/Solution.sln\"", result.Args);
10151016
}
10161017

1018+
[Fact]
1019+
public void Should_Append_GetProperty_To_Process_Arguments_And_Collects_Output()
1020+
{
1021+
IEnumerable<string> msbuildOutput = null;
1022+
1023+
// Given
1024+
var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows);
1025+
fixture.Settings.WithGetProperty("A");
1026+
fixture.Settings.WithGetProperty("B");
1027+
fixture.StandardOutputAction = lines => msbuildOutput = lines;
1028+
var standardOutput = new string[]
1029+
{
1030+
"{",
1031+
" \"Properties\": {",
1032+
" \"A\": \"A value\",",
1033+
" \"B\": \"B value\"",
1034+
" }",
1035+
"}",
1036+
};
1037+
fixture.ProcessRunner.Process.SetStandardOutput(standardOutput);
1038+
1039+
// When
1040+
var result = fixture.Run();
1041+
1042+
// Then
1043+
Assert.Equal("/v:normal /target:Build /getProperty:A /getProperty:B " +
1044+
"\"C:/Working/src/Solution.sln\"", result.Args);
1045+
1046+
Assert.Equal(standardOutput, msbuildOutput);
1047+
}
1048+
1049+
[Fact]
1050+
public void Should_Append_GetItem_To_Process_Arguments_And_Collects_Output()
1051+
{
1052+
IEnumerable<string> msbuildOutput = null;
1053+
1054+
// Given
1055+
var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows);
1056+
fixture.Settings.WithGetItem("A");
1057+
fixture.Settings.WithGetItem("B");
1058+
fixture.StandardOutputAction = lines => msbuildOutput = lines;
1059+
var standardOutput = new string[]
1060+
{
1061+
"{",
1062+
" \"Items\": {",
1063+
" \"A\": [",
1064+
" {",
1065+
" \"Identity\": \"Identity value\"",
1066+
" }",
1067+
" ],",
1068+
" \"B\": [",
1069+
" {",
1070+
" \"Identity\": \"Identity value\"",
1071+
" }",
1072+
" ],",
1073+
" }",
1074+
"}",
1075+
};
1076+
fixture.ProcessRunner.Process.SetStandardOutput(standardOutput);
1077+
1078+
// When
1079+
var result = fixture.Run();
1080+
1081+
// Then
1082+
Assert.Equal("/v:normal /target:Build /getItem:A /getItem:B " +
1083+
"\"C:/Working/src/Solution.sln\"", result.Args);
1084+
1085+
Assert.Equal(standardOutput, msbuildOutput);
1086+
}
1087+
1088+
[Fact]
1089+
public void Should_Append_GetTargetResult_To_Process_Arguments_And_Collects_Output()
1090+
{
1091+
IEnumerable<string> msbuildOutput = null;
1092+
1093+
// Given
1094+
var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows);
1095+
fixture.Settings.WithGetTargetResult("A");
1096+
fixture.Settings.WithGetTargetResult("B");
1097+
fixture.StandardOutputAction = lines => msbuildOutput = lines;
1098+
var standardOutput = new string[]
1099+
{
1100+
"{",
1101+
" \"TargetResults\": {",
1102+
" \"A\": {",
1103+
" \"Result\": \"Success\"",
1104+
" \"Items\": []",
1105+
" },",
1106+
" \"B\": {",
1107+
" \"Result\": \"Success\"",
1108+
" \"Items\": []",
1109+
" }",
1110+
" }",
1111+
"}",
1112+
};
1113+
fixture.ProcessRunner.Process.SetStandardOutput(standardOutput);
1114+
1115+
// When
1116+
var result = fixture.Run();
1117+
1118+
// Then
1119+
Assert.Equal("/v:normal /target:Build /getTargetResult:A /getTargetResult:B " +
1120+
"\"C:/Working/src/Solution.sln\"", result.Args);
1121+
1122+
Assert.Equal(standardOutput, msbuildOutput);
1123+
}
1124+
10171125
[Theory]
10181126
[InlineData("Release", "/v:normal /p:Configuration=\"Release\" /target:Build \"C:/Working/src/Solution.sln\"")]
10191127
[InlineData("Custom Spaced", "/v:normal /p:Configuration=\"Custom Spaced\" /target:Build \"C:/Working/src/Solution.sln\"")]

src/Cake.Common/Tools/DotNet/DotNetAliases.MSBuild.cs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Collections.Generic;
67
using Cake.Common.Tools.DotNet.MSBuild;
78
using Cake.Core;
89
using Cake.Core.Annotations;
@@ -33,7 +34,7 @@ public static partial class DotNetAliases
3334
[CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")]
3435
public static void DotNetMSBuild(this ICakeContext context)
3536
{
36-
context.DotNetMSBuild(null, null);
37+
context.DotNetMSBuild((string)null, (DotNetMSBuildSettings)null);
3738
}
3839

3940
/// <summary>
@@ -86,6 +87,32 @@ public static void DotNetMSBuild(this ICakeContext context, DotNetMSBuildSetting
8687
context.DotNetMSBuild(null, settings);
8788
}
8889

90+
/// <summary>
91+
/// Builds the specified targets in a project file found in the current working directory.
92+
/// </summary>
93+
/// <param name="context">The context.</param>
94+
/// <param name="settings">The settings.</param>
95+
/// <param name="standardOutputAction">The action to invoke with the standard output.</param>
96+
/// <example>
97+
/// <code>
98+
/// var settings = new DotNetMSBuildSettings
99+
/// {
100+
/// NoLogo = true,
101+
/// MaxCpuCount = -1
102+
/// };
103+
///
104+
/// DotNetMSBuild(settings,
105+
/// output => foreach(var line in output) outputBuilder.AppendLine(line));
106+
/// </code>
107+
/// </example>
108+
[CakeMethodAlias]
109+
[CakeAliasCategory("MSBuild")]
110+
[CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")]
111+
public static void DotNetMSBuild(this ICakeContext context, DotNetMSBuildSettings settings, Action<IEnumerable<string>> standardOutputAction)
112+
{
113+
context.DotNetMSBuild(null, settings, standardOutputAction);
114+
}
115+
89116
/// <summary>
90117
/// Builds the specified targets in the project file.
91118
/// </summary>
@@ -123,7 +150,49 @@ public static void DotNetMSBuild(this ICakeContext context, string projectOrDire
123150
}
124151

125152
var builder = new DotNetMSBuildBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools);
126-
builder.Build(projectOrDirectory, settings);
153+
builder.Build(projectOrDirectory, settings, null);
154+
}
155+
156+
/// <summary>
157+
/// Builds the specified targets in the project file.
158+
/// </summary>
159+
/// <param name="context">The context.</param>
160+
/// <param name="projectOrDirectory">Project file or directory to search for project file.</param>
161+
/// <param name="settings">The settings.</param>
162+
/// <param name="standardOutputAction">The action to invoke with the standard output.</param>
163+
/// <example>
164+
/// <code>
165+
/// var settings = new DotNetMSBuildSettings
166+
/// {
167+
/// NoLogo = true,
168+
/// MaxCpuCount = -1
169+
/// };
170+
///
171+
/// DotNetMSBuild("foobar.proj", settings,
172+
/// output => foreach(var line in output) outputBuilder.AppendLine(line));
173+
/// </code>
174+
/// </example>
175+
/// <remarks>
176+
/// If a project file is not specified, MSBuild searches the current working directory for a file that has a file
177+
/// extension that ends in "proj" and uses that file. If a directory is specified, MSBuild searches that directory for a project file.
178+
/// </remarks>
179+
[CakeMethodAlias]
180+
[CakeAliasCategory("MSBuild")]
181+
[CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")]
182+
public static void DotNetMSBuild(this ICakeContext context, string projectOrDirectory, DotNetMSBuildSettings settings, Action<IEnumerable<string>> standardOutputAction)
183+
{
184+
if (context is null)
185+
{
186+
throw new ArgumentNullException(nameof(context));
187+
}
188+
189+
if (settings is null)
190+
{
191+
settings = new DotNetMSBuildSettings();
192+
}
193+
194+
var builder = new DotNetMSBuildBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools);
195+
builder.Build(projectOrDirectory, settings, standardOutputAction);
127196
}
128197
}
129198
}

0 commit comments

Comments
 (0)