Skip to content

Commit

Permalink
Add UsingTaskRoslynCodeTaskFactory convenience method (#304)
Browse files Browse the repository at this point in the history
  • Loading branch information
MattKotsenas authored Oct 18, 2024
1 parent 76d81fa commit eb19fcb
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using Microsoft.Build.Evaluation;
using Shouldly;
using System.IO;
using Xunit;

namespace Microsoft.Build.Utilities.ProjectCreation.UnitTests
Expand Down Expand Up @@ -92,6 +93,101 @@ public void UsingTaskComplexParameters()
StringCompareShould.IgnoreLineEndings);
}

[Theory]
[InlineData("""Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");""")]
[InlineData("""<![CDATA[Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");]]>""")]
public void UsingTaskInlineFragmentSimple(string code)
{
ProjectCreator.Create(projectFileOptions: NewProjectFileOptions.None)
.UsingTaskRoslynCodeTaskFactory("MySample", code)
.Xml
.ShouldBe(
$"""
<Project>
<UsingTask TaskName="MySample" AssemblyFile="{Path.Combine("$(MSBuildToolsPath)", "Microsoft.Build.Tasks.Core.dll")}" TaskFactory="RoslynCodeTaskFactory">
<Task>
<Code Type="Fragment" Language="cs"><![CDATA[Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");]]></Code>
</Task>
</UsingTask>
</Project>
""",
StringCompareShould.IgnoreLineEndings);
}

[Fact]
public void UsingTaskInlineFragmentComplex()
{
ProjectCreator.Create(projectFileOptions: NewProjectFileOptions.None)
.UsingTaskRoslynCodeTaskFactory(
taskName: "MySample",
references: ["netstandard"],
usings: ["System"],
sourceCode: """
Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");
Log.LogMessageFromText($"Parameter1: '{Parameter1}'", MessageImportance.High);
Log.LogMessageFromText($"Parameter2: '{Parameter2}'", MessageImportance.High);
Parameter3 = "A value from the Roslyn CodeTaskFactory";
""")
.UsingTaskParameter(
name: "Parameter1",
parameterType: "System.String",
output: false,
required: true)
.UsingTaskParameter(
name: "Parameter2",
parameterType: "System.String",
output: false,
required: false)
.UsingTaskParameter(
name: "Parameter3",
parameterType: "System.String",
output: true,
required: false)
.Xml
.ShouldBe(
$@"<Project>
<UsingTask TaskName=""MySample"" AssemblyFile=""{Path.Combine("$(MSBuildToolsPath)", "Microsoft.Build.Tasks.Core.dll")}"" TaskFactory=""RoslynCodeTaskFactory"">
<ParameterGroup>
<Parameter1 Output=""False"" Required=""True"" ParameterType=""System.String"" />
<Parameter2 Output=""False"" Required=""False"" ParameterType=""System.String"" />
<Parameter3 Output=""True"" Required=""False"" ParameterType=""System.String"" />
</ParameterGroup>
<Task>
<Reference Include=""netstandard"" />
<Using Namespace=""System"" />
<Code Type=""Fragment"" Language=""cs""><![CDATA[Log.LogMessage(MessageImportance.High, ""Hello from an inline task created by Roslyn!"");
Log.LogMessageFromText($""Parameter1: '{{Parameter1}}'"", MessageImportance.High);
Log.LogMessageFromText($""Parameter2: '{{Parameter2}}'"", MessageImportance.High);
Parameter3 = ""A value from the Roslyn CodeTaskFactory"";]]></Code>
</Task>
</UsingTask>
</Project>",
StringCompareShould.IgnoreLineEndings);
}

[Fact]
public void UsingTaskInlineSource()
{
ProjectCreator.Create(projectFileOptions: NewProjectFileOptions.None)
.UsingTaskRoslynCodeTaskFactory(
taskName: "MySample",
sourcePath: "MySample.vb",
type: "Class",
language: "vb")
.Xml
.ShouldBe(
$"""
<Project>
<UsingTask TaskName="MySample" AssemblyFile="{Path.Combine("$(MSBuildToolsPath)", "Microsoft.Build.Tasks.Core.dll")}" TaskFactory="RoslynCodeTaskFactory">
<Task>
<Code Type="Class" Language="vb" Source="MySample.vb" />
</Task>
</UsingTask>
</Project>
""",
StringCompareShould.IgnoreLineEndings);
}

[Fact]
public void UsingTaskSimpleParameter()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// Licensed under the MIT license.

using Microsoft.Build.Construction;
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace Microsoft.Build.Utilities.ProjectCreation
{
Expand Down Expand Up @@ -109,5 +113,109 @@ public ProjectCreator UsingTaskParameter(string name, string? parameterType = nu

return this;
}

/// <summary>
/// Adds a &lt;UsingTask /&gt; with the TaskFactory set to "RoslynCodeTaskFactory" and the provided
/// code fragment or source file as the task body.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-roslyncodetaskfactory for
/// documentation on using a RoslynCodeTaskFactory.
/// </remarks>
/// <param name="taskName">The name of the task.</param>
/// <param name="sourceCode">C# or VB code to use as the task body. Mutually exclusive with <paramref name="sourcePath"/>.</param>
/// <param name="sourcePath">Path to a source to use as the task body. Mutually exclusive with <paramref name="sourceCode"/>.</param>
/// <param name="type">The type of code in the task body. Defaults to "Fragment", can also be "Method" or "Class".</param>
/// <param name="language">The source language. Defaults to "cs", can also be "vb".</param>
/// <param name="references">Paths to assemblies that should be added as references during compilation.</param>
/// <param name="usings">The list of namespaces to include as part of the compilation.</param>
/// <param name="taskFactory">The TaskFactory to use. Defaults to "RoslynCodeTaskFactory".</param>
/// <param name="runtime">An optional runtime for the task.</param>
/// <param name="architecture">An optional architecture for the task.</param>
/// <param name="condition">An optional condition to add to the task.</param>
/// <param name="label">An optional label to add to the task.</param>
/// <param name="evaluate">An optional value indicating if the body should be evaluated.</param>
/// <returns>The current <see cref="ProjectCreator" />.</returns>
public ProjectCreator UsingTaskRoslynCodeTaskFactory(
string taskName,
string? sourceCode = null,
string? sourcePath = null,
string type = "Fragment",
string language = "cs",
IEnumerable<string>? references = null,
IEnumerable<string>? usings = null,
string taskFactory = "RoslynCodeTaskFactory",
string? runtime = null,
string? architecture = null,
string? condition = null,
string? label = null,
bool? evaluate = null)
{
if (sourceCode is null && sourcePath is null)
{
throw new ProjectCreatorException(Strings.ErrorUsingTaskRoslynCodeTaskFactoryRequiresSourceCodeOrSourcePath);
}

if (sourceCode is not null && sourcePath is not null)
{
throw new ProjectCreatorException(Strings.ErrorUsingTaskRoslynCodeTaskFactoryRequiresSourceCodeOrSourcePath);
}

UsingTaskAssemblyFile(
taskName,
assemblyFile: @"$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll",
taskFactory,
runtime,
architecture,
condition,
label);

using StringWriter sw = new();
XmlWriterSettings settings = new()
{
ConformanceLevel = ConformanceLevel.Fragment,
OmitXmlDeclaration = true,
Indent = false, // If we don't indent Microsoft.Build.Construction will do it for us
};
using (XmlWriter writer = XmlWriter.Create(sw, settings))
{
foreach (string r in references ?? [])
{
writer.WriteStartElement("Reference");
writer.WriteAttributeString("Include", r);
writer.WriteEndElement(); // </Reference>
}

foreach (string u in usings ?? [])
{
writer.WriteStartElement("Using");
writer.WriteAttributeString("Namespace", u);
writer.WriteEndElement(); // </Using>
}

writer.WriteStartElement("Code");
writer.WriteAttributeString("Type", type);
writer.WriteAttributeString("Language", language);
writer.WriteAttributeStringIfNotNull("Source", sourcePath);

if (sourceCode is not null)
{
if (!sourceCode.AsSpan().TrimStart().StartsWith("<![CDATA[".AsSpan(), StringComparison.Ordinal))
{
writer.WriteCData(sourceCode);
}
else
{
writer.WriteRaw(sourceCode);
}
}

writer.WriteEndElement(); // </Code>
}

UsingTaskBody(sw.ToString(), evaluate);

return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyFile(s
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyName(string! taskName, string! assemblyName, string! taskFactory, string? runtime = null, string? architecture = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskBody(string! body, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskParameter(string! name, string? parameterType = null, bool? output = null, bool? required = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskRoslynCodeTaskFactory(string! taskName, string? sourceCode = null, string? sourcePath = null, string! type = "Fragment", string! language = "cs", System.Collections.Generic.IEnumerable<string!>? references = null, System.Collections.Generic.IEnumerable<string!>? usings = null, string! taskFactory = "RoslynCodeTaskFactory", string? runtime = null, string? architecture = null, string? condition = null, string? label = null, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.When(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemGroup(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemInclude(string! itemType, string! include, string? exclude = null, System.Collections.Generic.IDictionary<string!, string?>? metadata = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyFile(s
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyName(string! taskName, string! assemblyName, string! taskFactory, string? runtime = null, string? architecture = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskBody(string! body, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskParameter(string! name, string? parameterType = null, bool? output = null, bool? required = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskRoslynCodeTaskFactory(string! taskName, string? sourceCode = null, string? sourcePath = null, string! type = "Fragment", string! language = "cs", System.Collections.Generic.IEnumerable<string!>? references = null, System.Collections.Generic.IEnumerable<string!>? usings = null, string! taskFactory = "RoslynCodeTaskFactory", string? runtime = null, string? architecture = null, string? condition = null, string? label = null, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.When(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemGroup(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemInclude(string! itemType, string! include, string? exclude = null, System.Collections.Generic.IDictionary<string!, string?>? metadata = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyFile(s
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyName(string! taskName, string! assemblyName, string! taskFactory, string? runtime = null, string? architecture = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskBody(string! body, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskParameter(string! name, string? parameterType = null, bool? output = null, bool? required = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskRoslynCodeTaskFactory(string! taskName, string? sourceCode = null, string? sourcePath = null, string! type = "Fragment", string! language = "cs", System.Collections.Generic.IEnumerable<string!>? references = null, System.Collections.Generic.IEnumerable<string!>? usings = null, string! taskFactory = "RoslynCodeTaskFactory", string? runtime = null, string? architecture = null, string? condition = null, string? label = null, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.When(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemGroup(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemInclude(string! itemType, string! include, string? exclude = null, System.Collections.Generic.IDictionary<string!, string?>? metadata = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyFile(s
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyName(string! taskName, string! assemblyName, string! taskFactory, string? runtime = null, string? architecture = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskBody(string! body, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskParameter(string! name, string? parameterType = null, bool? output = null, bool? required = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskRoslynCodeTaskFactory(string! taskName, string? sourceCode = null, string? sourcePath = null, string! type = "Fragment", string! language = "cs", System.Collections.Generic.IEnumerable<string!>? references = null, System.Collections.Generic.IEnumerable<string!>? usings = null, string! taskFactory = "RoslynCodeTaskFactory", string? runtime = null, string? architecture = null, string? condition = null, string? label = null, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.When(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemGroup(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemInclude(string! itemType, string! include, string? exclude = null, System.Collections.Generic.IDictionary<string!, string?>? metadata = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.Build.Utilities.ProjectCreation/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,7 @@
<data name="ErrorWhenPropertyGroupRequiresWhen" xml:space="preserve">
<value>You must add a When before adding a When PropertyGroup.</value>
</data>
<data name="ErrorUsingTaskRoslynCodeTaskFactoryRequiresSourceCodeOrSourcePath" xml:space="preserve">
<value>You must specify either inline source code or a path to a source file, but not both.</value>
</data>
</root>

0 comments on commit eb19fcb

Please sign in to comment.