From bb503f50870885fe31a9b6d5649d2fcec4091a10 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Fri, 18 Oct 2024 14:53:51 -0700 Subject: [PATCH] Add UsingTaskRoslynCodeTaskFactory convenience method (#304) --- .../UsingTaskTests.cs | 96 ++++++++++++++++ .../ProjectCreator.UsingTasks.cs | 108 ++++++++++++++++++ .../PublicAPI/net472/PublicAPI.Shipped.txt | 1 + .../PublicAPI/net6.0/PublicAPI.Shipped.txt | 1 + .../PublicAPI/net8.0/PublicAPI.Shipped.txt | 1 + .../PublicAPI/net9.0/PublicAPI.Shipped.txt | 1 + .../Strings.Designer.cs | 9 ++ .../Strings.resx | 3 + 8 files changed, 220 insertions(+) diff --git a/src/Microsoft.Build.Utilities.ProjectCreation.UnitTests/UsingTaskTests.cs b/src/Microsoft.Build.Utilities.ProjectCreation.UnitTests/UsingTaskTests.cs index 95cf78c..4f216a3 100644 --- a/src/Microsoft.Build.Utilities.ProjectCreation.UnitTests/UsingTaskTests.cs +++ b/src/Microsoft.Build.Utilities.ProjectCreation.UnitTests/UsingTaskTests.cs @@ -4,6 +4,7 @@ using Microsoft.Build.Evaluation; using Shouldly; +using System.IO; using Xunit; namespace Microsoft.Build.Utilities.ProjectCreation.UnitTests @@ -92,6 +93,101 @@ public void UsingTaskComplexParameters() StringCompareShould.IgnoreLineEndings); } + [Theory] + [InlineData("""Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");""")] + [InlineData("""""")] + public void UsingTaskInlineFragmentSimple(string code) + { + ProjectCreator.Create(projectFileOptions: NewProjectFileOptions.None) + .UsingTaskRoslynCodeTaskFactory("MySample", code) + .Xml + .ShouldBe( + $""" + + + + + + + + """, + 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( + $@" + + + + + + + + + + + + +", + StringCompareShould.IgnoreLineEndings); + } + + [Fact] + public void UsingTaskInlineSource() + { + ProjectCreator.Create(projectFileOptions: NewProjectFileOptions.None) + .UsingTaskRoslynCodeTaskFactory( + taskName: "MySample", + sourcePath: "MySample.vb", + type: "Class", + language: "vb") + .Xml + .ShouldBe( + $""" + + + + + + + + """, + StringCompareShould.IgnoreLineEndings); + } + [Fact] public void UsingTaskSimpleParameter() { diff --git a/src/Microsoft.Build.Utilities.ProjectCreation/ProjectCreator.UsingTasks.cs b/src/Microsoft.Build.Utilities.ProjectCreation/ProjectCreator.UsingTasks.cs index 56da5ac..195b4ea 100644 --- a/src/Microsoft.Build.Utilities.ProjectCreation/ProjectCreator.UsingTasks.cs +++ b/src/Microsoft.Build.Utilities.ProjectCreation/ProjectCreator.UsingTasks.cs @@ -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 { @@ -109,5 +113,109 @@ public ProjectCreator UsingTaskParameter(string name, string? parameterType = nu return this; } + + /// + /// Adds a <UsingTask /> with the TaskFactory set to "RoslynCodeTaskFactory" and the provided + /// code fragment or source file as the task body. + /// + /// + /// See https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-roslyncodetaskfactory for + /// documentation on using a RoslynCodeTaskFactory. + /// + /// The name of the task. + /// C# or VB code to use as the task body. Mutually exclusive with . + /// Path to a source to use as the task body. Mutually exclusive with . + /// The type of code in the task body. Defaults to "Fragment", can also be "Method" or "Class". + /// The source language. Defaults to "cs", can also be "vb". + /// Paths to assemblies that should be added as references during compilation. + /// The list of namespaces to include as part of the compilation. + /// The TaskFactory to use. Defaults to "RoslynCodeTaskFactory". + /// An optional runtime for the task. + /// An optional architecture for the task. + /// An optional condition to add to the task. + /// An optional label to add to the task. + /// An optional value indicating if the body should be evaluated. + /// The current . + public ProjectCreator UsingTaskRoslynCodeTaskFactory( + string taskName, + string? sourceCode = null, + string? sourcePath = null, + string type = "Fragment", + string language = "cs", + IEnumerable? references = null, + IEnumerable? 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(); // + } + + foreach (string u in usings ?? []) + { + writer.WriteStartElement("Using"); + writer.WriteAttributeString("Namespace", u); + writer.WriteEndElement(); // + } + + 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(" + } + + UsingTaskBody(sw.ToString(), evaluate); + + return this; + } } } \ No newline at end of file diff --git a/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net472/PublicAPI.Shipped.txt b/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net472/PublicAPI.Shipped.txt index 62ce389..525fa50 100644 --- a/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net472/PublicAPI.Shipped.txt +++ b/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net472/PublicAPI.Shipped.txt @@ -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? references = null, System.Collections.Generic.IEnumerable? 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? metadata = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator! diff --git a/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net6.0/PublicAPI.Shipped.txt index b5d505d..37c0dfd 100644 --- a/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net6.0/PublicAPI.Shipped.txt +++ b/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net6.0/PublicAPI.Shipped.txt @@ -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? references = null, System.Collections.Generic.IEnumerable? 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? metadata = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator! diff --git a/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net8.0/PublicAPI.Shipped.txt b/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net8.0/PublicAPI.Shipped.txt index b5d505d..37c0dfd 100644 --- a/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net8.0/PublicAPI.Shipped.txt +++ b/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net8.0/PublicAPI.Shipped.txt @@ -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? references = null, System.Collections.Generic.IEnumerable? 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? metadata = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator! diff --git a/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net9.0/PublicAPI.Shipped.txt b/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net9.0/PublicAPI.Shipped.txt index b5d505d..37c0dfd 100644 --- a/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net9.0/PublicAPI.Shipped.txt +++ b/src/Microsoft.Build.Utilities.ProjectCreation/PublicAPI/net9.0/PublicAPI.Shipped.txt @@ -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? references = null, System.Collections.Generic.IEnumerable? 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? metadata = null, string? condition = null, string? label = null) -> Microsoft.Build.Utilities.ProjectCreation.ProjectCreator! diff --git a/src/Microsoft.Build.Utilities.ProjectCreation/Strings.Designer.cs b/src/Microsoft.Build.Utilities.ProjectCreation/Strings.Designer.cs index e71fb82..f093dda 100644 --- a/src/Microsoft.Build.Utilities.ProjectCreation/Strings.Designer.cs +++ b/src/Microsoft.Build.Utilities.ProjectCreation/Strings.Designer.cs @@ -159,6 +159,15 @@ internal static string ErrorUsingTaskParameterRequiresUsingTask { } } + /// + /// Looks up a localized string similar to You must specify either inline source code or a path to a source file, but not both.. + /// + internal static string ErrorUsingTaskRoslynCodeTaskFactoryRequiresSourceCodeOrSourcePath { + get { + return ResourceManager.GetString("ErrorUsingTaskRoslynCodeTaskFactoryRequiresSourceCodeOrSourcePath", resourceCulture); + } + } + /// /// Looks up a localized string similar to You must add a package to a feed before adding content.. /// diff --git a/src/Microsoft.Build.Utilities.ProjectCreation/Strings.resx b/src/Microsoft.Build.Utilities.ProjectCreation/Strings.resx index 3c3b108..b42753c 100644 --- a/src/Microsoft.Build.Utilities.ProjectCreation/Strings.resx +++ b/src/Microsoft.Build.Utilities.ProjectCreation/Strings.resx @@ -165,4 +165,7 @@ You must add a When before adding a When PropertyGroup. + + You must specify either inline source code or a path to a source file, but not both. + \ No newline at end of file