Skip to content

Commit

Permalink
Add support for capturing MSBuild properties, items and target results
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
paulomorgado authored and devlead committed Jan 6, 2025
1 parent db1abc6 commit 6a26f31
Show file tree
Hide file tree
Showing 13 changed files with 703 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
// 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
{
internal sealed class DotNetMSBuildBuilderFixture : DotNetFixture<DotNetMSBuildSettings>
{
public string Project { get; set; }
public Action<IEnumerable<string>> StandardOutputAction { get; set; }

protected override void RunTool()
{
var tool = new DotNetMSBuildBuilder(FileSystem, Environment, ProcessRunner, Tools);
tool.Build(Project, Settings);
tool.Build(Project, Settings, StandardOutputAction);
}
}
}
4 changes: 3 additions & 1 deletion src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,6 +16,7 @@ internal sealed class MSBuildRunnerFixture : ToolFixture<MSBuildSettings>
{
public HashSet<FilePath> KnownMSBuildPaths { get; }
public FilePath Solution { get; set; }
public Action<IEnumerable<string>> StandardOutputAction { get; set; }

public MSBuildRunnerFixture(bool is64BitOperativeSystem, PlatformFamily platformFamily)
: base("MSBuild.exe")
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string> 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<string> 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<string> 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)]
Expand Down
108 changes: 108 additions & 0 deletions src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string> 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<string> 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<string> 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\"")]
Expand Down
73 changes: 71 additions & 2 deletions src/Cake.Common/Tools/DotNet/DotNetAliases.MSBuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using Cake.Common.Tools.DotNet.MSBuild;
using Cake.Core;
using Cake.Core.Annotations;
Expand Down Expand Up @@ -33,7 +34,7 @@ public static partial class DotNetAliases
[CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")]
public static void DotNetMSBuild(this ICakeContext context)
{
context.DotNetMSBuild(null, null);
context.DotNetMSBuild((string)null, (DotNetMSBuildSettings)null);
}

/// <summary>
Expand Down Expand Up @@ -86,6 +87,32 @@ public static void DotNetMSBuild(this ICakeContext context, DotNetMSBuildSetting
context.DotNetMSBuild(null, settings);
}

/// <summary>
/// Builds the specified targets in a project file found in the current working directory.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="settings">The settings.</param>
/// <param name="standardOutputAction">The action to invoke with the standard output.</param>
/// <example>
/// <code>
/// var settings = new DotNetMSBuildSettings
/// {
/// NoLogo = true,
/// MaxCpuCount = -1
/// };
///
/// DotNetMSBuild(settings,
/// output => foreach(var line in output) outputBuilder.AppendLine(line));
/// </code>
/// </example>
[CakeMethodAlias]
[CakeAliasCategory("MSBuild")]
[CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")]
public static void DotNetMSBuild(this ICakeContext context, DotNetMSBuildSettings settings, Action<IEnumerable<string>> standardOutputAction)
{
context.DotNetMSBuild(null, settings, standardOutputAction);
}

/// <summary>
/// Builds the specified targets in the project file.
/// </summary>
Expand Down Expand Up @@ -123,7 +150,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);
}

/// <summary>
/// Builds the specified targets in the project file.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="projectOrDirectory">Project file or directory to search for project file.</param>
/// <param name="settings">The settings.</param>
/// <param name="standardOutputAction">The action to invoke with the standard output.</param>
/// <example>
/// <code>
/// var settings = new DotNetMSBuildSettings
/// {
/// NoLogo = true,
/// MaxCpuCount = -1
/// };
///
/// DotNetMSBuild("foobar.proj", settings,
/// output => foreach(var line in output) outputBuilder.AppendLine(line));
/// </code>
/// </example>
/// <remarks>
/// 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.
/// </remarks>
[CakeMethodAlias]
[CakeAliasCategory("MSBuild")]
[CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")]
public static void DotNetMSBuild(this ICakeContext context, string projectOrDirectory, DotNetMSBuildSettings settings, Action<IEnumerable<string>> 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);
}
}
}
Loading

0 comments on commit 6a26f31

Please sign in to comment.