Skip to content

Commit eb19fcb

Browse files
authored
Add UsingTaskRoslynCodeTaskFactory convenience method (#304)
1 parent 76d81fa commit eb19fcb

File tree

8 files changed

+220
-0
lines changed

8 files changed

+220
-0
lines changed

src/Microsoft.Build.Utilities.ProjectCreation.UnitTests/UsingTaskTests.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using Microsoft.Build.Evaluation;
66
using Shouldly;
7+
using System.IO;
78
using Xunit;
89

910
namespace Microsoft.Build.Utilities.ProjectCreation.UnitTests
@@ -92,6 +93,101 @@ public void UsingTaskComplexParameters()
9293
StringCompareShould.IgnoreLineEndings);
9394
}
9495

96+
[Theory]
97+
[InlineData("""Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");""")]
98+
[InlineData("""<![CDATA[Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");]]>""")]
99+
public void UsingTaskInlineFragmentSimple(string code)
100+
{
101+
ProjectCreator.Create(projectFileOptions: NewProjectFileOptions.None)
102+
.UsingTaskRoslynCodeTaskFactory("MySample", code)
103+
.Xml
104+
.ShouldBe(
105+
$"""
106+
<Project>
107+
<UsingTask TaskName="MySample" AssemblyFile="{Path.Combine("$(MSBuildToolsPath)", "Microsoft.Build.Tasks.Core.dll")}" TaskFactory="RoslynCodeTaskFactory">
108+
<Task>
109+
<Code Type="Fragment" Language="cs"><![CDATA[Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");]]></Code>
110+
</Task>
111+
</UsingTask>
112+
</Project>
113+
""",
114+
StringCompareShould.IgnoreLineEndings);
115+
}
116+
117+
[Fact]
118+
public void UsingTaskInlineFragmentComplex()
119+
{
120+
ProjectCreator.Create(projectFileOptions: NewProjectFileOptions.None)
121+
.UsingTaskRoslynCodeTaskFactory(
122+
taskName: "MySample",
123+
references: ["netstandard"],
124+
usings: ["System"],
125+
sourceCode: """
126+
Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");
127+
Log.LogMessageFromText($"Parameter1: '{Parameter1}'", MessageImportance.High);
128+
Log.LogMessageFromText($"Parameter2: '{Parameter2}'", MessageImportance.High);
129+
Parameter3 = "A value from the Roslyn CodeTaskFactory";
130+
""")
131+
.UsingTaskParameter(
132+
name: "Parameter1",
133+
parameterType: "System.String",
134+
output: false,
135+
required: true)
136+
.UsingTaskParameter(
137+
name: "Parameter2",
138+
parameterType: "System.String",
139+
output: false,
140+
required: false)
141+
.UsingTaskParameter(
142+
name: "Parameter3",
143+
parameterType: "System.String",
144+
output: true,
145+
required: false)
146+
.Xml
147+
.ShouldBe(
148+
$@"<Project>
149+
<UsingTask TaskName=""MySample"" AssemblyFile=""{Path.Combine("$(MSBuildToolsPath)", "Microsoft.Build.Tasks.Core.dll")}"" TaskFactory=""RoslynCodeTaskFactory"">
150+
<ParameterGroup>
151+
<Parameter1 Output=""False"" Required=""True"" ParameterType=""System.String"" />
152+
<Parameter2 Output=""False"" Required=""False"" ParameterType=""System.String"" />
153+
<Parameter3 Output=""True"" Required=""False"" ParameterType=""System.String"" />
154+
</ParameterGroup>
155+
<Task>
156+
<Reference Include=""netstandard"" />
157+
<Using Namespace=""System"" />
158+
<Code Type=""Fragment"" Language=""cs""><![CDATA[Log.LogMessage(MessageImportance.High, ""Hello from an inline task created by Roslyn!"");
159+
Log.LogMessageFromText($""Parameter1: '{{Parameter1}}'"", MessageImportance.High);
160+
Log.LogMessageFromText($""Parameter2: '{{Parameter2}}'"", MessageImportance.High);
161+
Parameter3 = ""A value from the Roslyn CodeTaskFactory"";]]></Code>
162+
</Task>
163+
</UsingTask>
164+
</Project>",
165+
StringCompareShould.IgnoreLineEndings);
166+
}
167+
168+
[Fact]
169+
public void UsingTaskInlineSource()
170+
{
171+
ProjectCreator.Create(projectFileOptions: NewProjectFileOptions.None)
172+
.UsingTaskRoslynCodeTaskFactory(
173+
taskName: "MySample",
174+
sourcePath: "MySample.vb",
175+
type: "Class",
176+
language: "vb")
177+
.Xml
178+
.ShouldBe(
179+
$"""
180+
<Project>
181+
<UsingTask TaskName="MySample" AssemblyFile="{Path.Combine("$(MSBuildToolsPath)", "Microsoft.Build.Tasks.Core.dll")}" TaskFactory="RoslynCodeTaskFactory">
182+
<Task>
183+
<Code Type="Class" Language="vb" Source="MySample.vb" />
184+
</Task>
185+
</UsingTask>
186+
</Project>
187+
""",
188+
StringCompareShould.IgnoreLineEndings);
189+
}
190+
95191
[Fact]
96192
public void UsingTaskSimpleParameter()
97193
{

src/Microsoft.Build.Utilities.ProjectCreation/ProjectCreator.UsingTasks.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
// Licensed under the MIT license.
44

55
using Microsoft.Build.Construction;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.IO;
9+
using System.Xml;
610

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

110114
return this;
111115
}
116+
117+
/// <summary>
118+
/// Adds a &lt;UsingTask /&gt; with the TaskFactory set to "RoslynCodeTaskFactory" and the provided
119+
/// code fragment or source file as the task body.
120+
/// </summary>
121+
/// <remarks>
122+
/// See https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-roslyncodetaskfactory for
123+
/// documentation on using a RoslynCodeTaskFactory.
124+
/// </remarks>
125+
/// <param name="taskName">The name of the task.</param>
126+
/// <param name="sourceCode">C# or VB code to use as the task body. Mutually exclusive with <paramref name="sourcePath"/>.</param>
127+
/// <param name="sourcePath">Path to a source to use as the task body. Mutually exclusive with <paramref name="sourceCode"/>.</param>
128+
/// <param name="type">The type of code in the task body. Defaults to "Fragment", can also be "Method" or "Class".</param>
129+
/// <param name="language">The source language. Defaults to "cs", can also be "vb".</param>
130+
/// <param name="references">Paths to assemblies that should be added as references during compilation.</param>
131+
/// <param name="usings">The list of namespaces to include as part of the compilation.</param>
132+
/// <param name="taskFactory">The TaskFactory to use. Defaults to "RoslynCodeTaskFactory".</param>
133+
/// <param name="runtime">An optional runtime for the task.</param>
134+
/// <param name="architecture">An optional architecture for the task.</param>
135+
/// <param name="condition">An optional condition to add to the task.</param>
136+
/// <param name="label">An optional label to add to the task.</param>
137+
/// <param name="evaluate">An optional value indicating if the body should be evaluated.</param>
138+
/// <returns>The current <see cref="ProjectCreator" />.</returns>
139+
public ProjectCreator UsingTaskRoslynCodeTaskFactory(
140+
string taskName,
141+
string? sourceCode = null,
142+
string? sourcePath = null,
143+
string type = "Fragment",
144+
string language = "cs",
145+
IEnumerable<string>? references = null,
146+
IEnumerable<string>? usings = null,
147+
string taskFactory = "RoslynCodeTaskFactory",
148+
string? runtime = null,
149+
string? architecture = null,
150+
string? condition = null,
151+
string? label = null,
152+
bool? evaluate = null)
153+
{
154+
if (sourceCode is null && sourcePath is null)
155+
{
156+
throw new ProjectCreatorException(Strings.ErrorUsingTaskRoslynCodeTaskFactoryRequiresSourceCodeOrSourcePath);
157+
}
158+
159+
if (sourceCode is not null && sourcePath is not null)
160+
{
161+
throw new ProjectCreatorException(Strings.ErrorUsingTaskRoslynCodeTaskFactoryRequiresSourceCodeOrSourcePath);
162+
}
163+
164+
UsingTaskAssemblyFile(
165+
taskName,
166+
assemblyFile: @"$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll",
167+
taskFactory,
168+
runtime,
169+
architecture,
170+
condition,
171+
label);
172+
173+
using StringWriter sw = new();
174+
XmlWriterSettings settings = new()
175+
{
176+
ConformanceLevel = ConformanceLevel.Fragment,
177+
OmitXmlDeclaration = true,
178+
Indent = false, // If we don't indent Microsoft.Build.Construction will do it for us
179+
};
180+
using (XmlWriter writer = XmlWriter.Create(sw, settings))
181+
{
182+
foreach (string r in references ?? [])
183+
{
184+
writer.WriteStartElement("Reference");
185+
writer.WriteAttributeString("Include", r);
186+
writer.WriteEndElement(); // </Reference>
187+
}
188+
189+
foreach (string u in usings ?? [])
190+
{
191+
writer.WriteStartElement("Using");
192+
writer.WriteAttributeString("Namespace", u);
193+
writer.WriteEndElement(); // </Using>
194+
}
195+
196+
writer.WriteStartElement("Code");
197+
writer.WriteAttributeString("Type", type);
198+
writer.WriteAttributeString("Language", language);
199+
writer.WriteAttributeStringIfNotNull("Source", sourcePath);
200+
201+
if (sourceCode is not null)
202+
{
203+
if (!sourceCode.AsSpan().TrimStart().StartsWith("<![CDATA[".AsSpan(), StringComparison.Ordinal))
204+
{
205+
writer.WriteCData(sourceCode);
206+
}
207+
else
208+
{
209+
writer.WriteRaw(sourceCode);
210+
}
211+
}
212+
213+
writer.WriteEndElement(); // </Code>
214+
}
215+
216+
UsingTaskBody(sw.ToString(), evaluate);
217+
218+
return this;
219+
}
112220
}
113221
}

src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net472/PublicAPI.Shipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyFile(s
266266
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!
267267
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskBody(string! body, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
268268
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskParameter(string! name, string? parameterType = null, bool? output = null, bool? required = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
269+
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!
269270
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.When(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
270271
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemGroup(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
271272
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!

src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net6.0/PublicAPI.Shipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyFile(s
266266
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!
267267
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskBody(string! body, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
268268
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskParameter(string! name, string? parameterType = null, bool? output = null, bool? required = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
269+
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!
269270
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.When(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
270271
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemGroup(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
271272
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!

src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net8.0/PublicAPI.Shipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyFile(s
266266
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!
267267
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskBody(string! body, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
268268
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskParameter(string! name, string? parameterType = null, bool? output = null, bool? required = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
269+
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!
269270
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.When(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
270271
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemGroup(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
271272
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!

src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net9.0/PublicAPI.Shipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskAssemblyFile(s
266266
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!
267267
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskBody(string! body, bool? evaluate = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
268268
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.UsingTaskParameter(string! name, string? parameterType = null, bool? output = null, bool? required = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
269+
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!
269270
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.When(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
270271
Microsoft.Build.Utilities.ProjectCreation.ProjectCreator.WhenItemGroup(string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator!
271272
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!

src/Microsoft.Build.Utilities.ProjectCreation/Strings.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Build.Utilities.ProjectCreation/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,7 @@
165165
<data name="ErrorWhenPropertyGroupRequiresWhen" xml:space="preserve">
166166
<value>You must add a When before adding a When PropertyGroup.</value>
167167
</data>
168+
<data name="ErrorUsingTaskRoslynCodeTaskFactoryRequiresSourceCodeOrSourcePath" xml:space="preserve">
169+
<value>You must specify either inline source code or a path to a source file, but not both.</value>
170+
</data>
168171
</root>

0 commit comments

Comments
 (0)