MSBuild 内联任务
通常,MSBuild 任务通过编译实现 ITask 接口的类进行创建。 有关详细信息,请参阅任务。
从 .NET Framework 版本 4 开始,你可以在项目文件中创建内联任务。 无需创建单独的程序集来承载该任务。 这使得更易于跟踪源代码和部署任务。 源代码将集成到脚本中。
在 MSBuild 15.8 中,添加了 RoslynCodeTaskFactory。 对于当前开发,请务必使用 RoslynCodeTaskFactory,而不是 CodeTaskFactory。 CodeTaskFactory 仅支持 4.0 以下的 C# 版本。
内联任务的结构
内联任务由 UsingTask 元素包含。 内联任务和包含它的 UsingTask
元素通常包括在 .targets 文件中,并根据需要导入到其他项目文件 。 下面是一个基本的内联任务。 请注意,它不执行任何操作。
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This simple inline task does nothing. -->
<UsingTask
TaskName="DoNothing"
TaskFactory="CodeTaskFactory"
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
属性来指定内联任务工厂类的完全限定的名称,该类通常位于$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll
中。
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
属性。
HelloWorld
下面是一个更全面的内联任务。 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="CodeTaskFactory"
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
特性,则将自动为每个参数创建属性。 否则,属性必须在源代码中显示声明,并且必须与其参数定义完全匹配。
示例
以下内联任务将给定文件中令牌的每个匹配项替换为给定的值。
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="15.0">
<UsingTask TaskName="TokenReplace" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<Token ParameterType="System.String" Required="true" />
<Replacement ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs"><![CDATA[
string content = File.ReadAllText(Path);
content = content.Replace(Token, Replacement);
File.WriteAllText(Path, content);
]]></Code>
</Task>
</UsingTask>
<Target Name='Demo' >
<TokenReplace Path="C:\Project\Target.config" Token="$MyToken$" Replacement="MyValue"/>
</Target>
</Project>