使用 RoslynCodeTaskFactory 创建 MSBuild 内联任务
RoslynCodeTaskFactory 与 CodeTaskFactory 类似,它使用跨平台的 Roslyn 编译器来生成内存中任务程序集用作内联任务。 RoslynCodeTaskFactory 任务面向的是 .NET Standard,它可用于 .NET Framework 和 .NET Core 运行时,还可用于 Linux 和 macOS 等其他平台。
备注
RoslynCodeTaskFactory 仅在 MSBuild 15.8 及更高版本中提供。 MSBuild 版本遵循 Visual Studio 版本,因此 RoslynCodeTaskFactory 在 Visual Studio 2017 版本 15.8 及更高版本版本中提供。
RoslynCodeTaskFactory 内联任务的声明方式与 CodeTaskFactory 的相同,唯一不同之处是它们面向 .NET Standard。 内联任务和包含它的 UsingTask
元素通常包括在 .targets 文件中,并根据需要导入到其他项目文件。 下面是一个基本的内联任务。 请注意,它不执行任何操作。
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This simple inline task does nothing. -->
<UsingTask
TaskName="DoNothing"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
<ParameterGroup />
<Task>
<Reference Include="" />
<Using Namespace="" />
<Code Type="Fragment" Language="cs">
</Code>
</Task>
</UsingTask>
</Project>
示例中的 UsingTask
元素具有三个属性,用于描述任务和编译该任务的内联任务工厂。
TaskName
属性命名任务,在本例中,即为DoNothing
。TaskFactory
属性命名实现内联任务工厂的类。AssemblyFile
属性提供内联任务工厂的位置。 或者,可以使用AssemblyName
属性来指定内联任务工厂类的完全限定的名称,它通常位于全局程序集缓存 (GAC) 中。
DoNothing
任务的其余元素为空,用于说明内联任务的顺序和结构。 本主题后面部分将提供更为全面的示例。
ParameterGroup
元素是可选的。 如果指定,它会声明任务的参数。 有关输入和输出参数的详细信息,请参阅本主题后面的输入和输出参数。Task
元素描述且包含任务源代码。Reference
元素指定对在代码中使用的 .NET 程序集的引用。 这相当于在 Visual Studio 中添加对项目的引用。Include
属性指定引用的程序集的路径。Using
元素列出你想要访问的命名空间。 这类似于 Visual C# 中的Using
语句。Namespace
属性指定要包含的命名空间。
Reference
和 Using
元素都与语言无关。 可以用任何一种受支持的 .NET CodeDom 语言(例如,Visual Basic 或 Visual C#)编写内联任务。
备注
由 Task
元素包含的元素均特定于任务工厂,在本例中,即代码任务工厂。
最后一个出现在 Task
元素内的子元素是 Code
元素。 Code
元素包含或定位你想要编译到任务中的代码。 放置于 Code
元素中的内容具体取决于你希望如何编写任务。
Language
属性指定编写代码的语言。 可接受的值为 cs
(对于 C#)或 vb
(对于 Visual Basic)。
Type
属性指定 Code
元素中找到的代码类型。
如果
Type
的值为Class
,则Code
元素将包含派生自 ITask 接口的类的代码。如果
Type
的值为Method
,则代码将定义 ITask 接口的Execute
方法的替代。如果
Type
的值为Fragment
,则代码将定义Execute
方法的内容,但不定义签名和return
语句。
通常,该代码本身会出现在 <![CDATA[
标记和 ]]>
标记之间。 由于代码位于 CDATA 部分,因此你不必担心转义保留字符(例如“<”或“>”)。
或者,可以使用 Code
元素的 Source
属性来指定包含任务代码的文件的位置。 源文件中的代码的类型必须为由 Type
属性所指定的类型。 如果存在 Source
属性,则 Type
的默认值为 Class
。 如果 Source
不存在,则默认值为 Fragment
。
备注
当在源文件中定义任务类时,类名必须符合对应的 UsingTask 元素的 TaskName
属性。
下面是使用 RoslynCodeTaskFactory 创建的内联任务,它的性能更可靠。 HelloWorld 任务在默认错误日志记录设备上显示“Hello, World!”,该设备通常为系统控制台或 Visual Studio 输出窗口。 示例中包含了 Reference
元素,这仅用于阐释目的。
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This simple inline task displays "Hello, world!" -->
<UsingTask
TaskName="HelloWorld"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
<ParameterGroup />
<Task>
<Reference Include="System.Xml"/>
<Using Namespace="System"/>
<Using Namespace="System.IO"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
// Display "Hello, world!"
Log.LogError("Hello, world!");
]]>
</Code>
</Task>
</UsingTask>
</Project>
可以将 HelloWorld 任务保存在名为 HelloWorld.targets 的文件中,然后按照如下所示从项目中调用它。
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="HelloWorld.targets" />
<Target Name="Hello">
<HelloWorld />
</Target>
</Project>
内联任务参数是 ParameterGroup
元素的子元素。 每个参数将定义它的元素的名称作为其名称。 以下代码定义参数 Text
。
<ParameterGroup>
<Text />
</ParameterGroup>
参数可能有以下一个或多个属性:
Required
是可选属性,默认值为false
。 如果为true
,则该参数是必需的,且必须在调用任务之前为其赋予值。ParameterType
是可选属性,默认值为System.String
。 可以将它设置为任何完全限定的类型(项或值),通过使用 System.Convert.ChangeType,可将其转换为字符串或从字符串转换为完全限定的类型。 (换言之,可传递至外部任务或可从外部任务传递的任何类型。)Output
是可选属性,默认值为false
。 如果为true
,则必须先为该参数赋予值,然后才能通过 Execute 方法返回。
例如,应用于对象的
<ParameterGroup>
<Expression Required="true" />
<Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<Tally ParameterType="System.Int32" Output="true" />
</ParameterGroup>
定义以下三个参数:
Expression
为必需的输入参数,其类型为 System.String。Files
是必需的项列表输入参数。Tally
是输出参数,其类型为 System.Int32。
如果 Code
元素具有 Fragment
或 Method
的 Type
特性,则将自动为每个参数创建属性。 在 RoslynCodeTaskFactory 中,如果 Code
元素具有 Class
的 Type
属性,则无需指定 ParameterGroup
,因为它是从源代码中推断出来的(这一点不同于 CodeTaskFactory
)。 否则,属性必须在源代码中显示声明,并且必须与其参数定义完全匹配。
以下内联任务记录部分消息并返回字符串。
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="15.0">
<UsingTask TaskName="MySample"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Parameter1 ParameterType="System.String" Required="true" />
<Parameter2 ParameterType="System.String" />
<Parameter3 ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<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>
<Target Name="Demo">
<MySample Parameter1="A value for parameter 1" Parameter2="A value for parameter 2">
<Output TaskParameter="Parameter3" PropertyName="NewProperty" />
</MySample>
<Message Text="NewProperty: '$(NewProperty)'" />
</Target>
</Project>
这些内联任务可合并路径并获取文件名。
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="15.0">
<UsingTask TaskName="PathCombine"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Paths ParameterType="System.String[]" Required="true" />
<Combined ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
Combined = Path.Combine(Paths);
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="PathGetFileName"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<FileName ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
FileName = System.IO.Path.GetFileName(Path);
]]>
</Code>
</Task>
</UsingTask>
<Target Name="Demo">
<PathCombine Paths="$(Temp);MyFolder;$([System.Guid]::NewGuid()).txt">
<Output TaskParameter="Combined" PropertyName="MyCombinedPaths" />
</PathCombine>
<Message Text="Combined Paths: '$(MyCombinedPaths)'" />
<PathGetFileName Path="$(MyCombinedPaths)">
<Output TaskParameter="FileName" PropertyName="MyFileName" />
</PathGetFileName>
<Message Text="File name: '$(MyFileName)'" />
</Target>
</Project>
RoslynCodeTaskFactory
最先在 MSBuild 版本 15.8 中提供。 假设你需要支持早期版本的 Visual Studio 和 MSBuild,但 RoslynCodeTaskFactory
不可用,而 CodeTaskFactory
可用,但你希望使用相同的生成脚本。 你可以使用 Choose
构造,该构造使用 $(MSBuildVersion)
属性来决定生成时是使用 RoslynCodeTaskFactory
还是回退到 CodeTaskFactory
,如以下示例中所示:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<Choose>
<When Condition=" '$(MSBuildVersion.Substring(0,2))' >= 16 Or
('$(MSBuildVersion.Substring(0,2))' == 15 And '$(MSBuildVersion.Substring(3,1))' >= 8)">
<PropertyGroup>
<TaskFactory>RoslynCodeTaskFactory</TaskFactory>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TaskFactory>CodeTaskFactory</TaskFactory>
</PropertyGroup>
</Otherwise>
</Choose>
<UsingTask
TaskName="HelloWorld"
TaskFactory="$(TaskFactory)"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup />
<Task>
<Using Namespace="System"/>
<Using Namespace="System.IO"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
Log.LogError("Using RoslynCodeTaskFactory");
]]>
</Code>
</Task>
</UsingTask>
<Target Name="RunTask" AfterTargets="Build">
<Message Text="MSBuildVersion: $(MSBuildVersion)"/>
<Message Text="TaskFactory: $(TaskFactory)"/>
<HelloWorld />
</Target>
</Project>