MSBuild 任务通常通过编译实现接口的 ITask 类来创建。 有关详细信息,请参阅 任务。
如果想要避免创建经过编译的任务的开销,可以在项目文件或导入文件中内嵌创建任务。 无需创建单独的程序集来托管任务。 使用内联任务可以更轻松地跟踪源代码并更轻松地部署任务。 源代码集成到 MSBuild 项目文件或导入的文件中,通常是一个 .targets 文件。
使用 代码任务工厂创建内联任务。 对于当前开发,请务必使用 RoslynCodeTaskFactory,而不是 CodeTaskFactory。
CodeTaskFactory 仅支持最多 4.0 的 C# 版本。
内联任务是为了方便处理不需要复杂依赖项的小型任务而设计的。 对内联任务的调试支持有限。 建议在想要编写更复杂的代码、引用 NuGet 包、运行外部工具或执行可能生成错误条件的作时创建已编译的任务,而不是内联任务。 此外,每次生成时都会编译内联任务,因此可能会对生成性能产生明显影响。
内联任务的结构
内联任务由 UsingTask 元素包含。 内联任务及包含它的 UsingTask 元素通常被包含在 .targets 文件中,并根据需要导入到其他项目文件。 下面是一个不执行任何操作的基本的内联任务,但展示了语法:
<!-- 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>
示例中 UsingTask 的元素有三个属性,用于描述任务和编译任务的内联任务工厂。
属性
TaskName将任务命名,在本例中为DoNothing该任务命名。属性
TaskFactory命名了实现内联任务工厂的类。该
AssemblyFile属性提供内联任务工厂的位置。 或者,可以使用AssemblyName属性指定内联任务工厂类的完全限定名称,该类通常位于内联任务工厂类中$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll。
任务的其余元素 DoNothing 是空的,用于说明内联任务的顺序和结构。 本文稍后将介绍完整的示例。
ParameterGroup元素是可选的。 指定后,它会声明任务的参数。 有关输入和输出参数的详细信息,请参阅本文后面的 输入和输出参数 。该
Task元素描述并包含任务源代码。该
Reference元素指定对代码中正在使用的 .NET 程序集的引用。 使用此元素等效于在 Visual Studio 中添加对项目的引用。 该Include特性指定所引用程序集的路径。 mscorlib、.NET Standard、Microsoft.Build.Framework 和 Microsoft.Build.Utilities.Core 中的程序集,以及一些传递引用为依赖项的程序集,均可在没有Reference的情况下使用。该
Using元素列出要访问的命名空间。 此元素等效于usingC# 中的指令。 该Namespace属性指定要包含的命名空间。 在内联代码中放入using指令是无效的,因为该代码被放入方法体中,而在方法体中不允许使用using指令。
Reference 和 Using 元素与语言无关。 内联任务可以用 Visual Basic 或 C# 编写。
注释
Task 元素所包含的元素是特定于任务工厂的,在本例中是代码任务工厂。
Code 元素
在 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 部分,因此无需担心转义保留字符,例如 < 或 >。
或者,可以使用 Source 元素的属性 Code 来指定包含任务代码的文件的位置。 源文件中的代码必须是由 Type 特性指定的类型。
Source如果该属性存在,则默认值为 TypeClass. 如果 Source 不存在,则默认值为 Fragment。
HelloWorld
下面是一个简单的内联任务示例。 HelloWorld 任务在默认错误日志记录设备上显示“Hello, world!” ,通常是系统控制台或 Visual Studio 输出 窗口。
<Project>
<!-- This simple inline task displays "Hello, world!" -->
<UsingTask
TaskName="HelloWorld"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
<ParameterGroup />
<Task>
<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>
<Import Project="HelloWorld.targets" />
<Target Name="Hello">
<HelloWorld />
</Target>
</Project>
输入和输出参数
内联任务参数是 ParameterGroup 元素的子元素。 每个参数都采用定义它的元素的名称。 以下代码定义参数 Text。
<ParameterGroup>
<Text />
</ParameterGroup>
参数可能具有以下一个或多个属性:
-
Required是默认的false可选属性。 如果是true,则参数是必需的,并且必须在调用任务之前提供一个值。 -
ParameterType是默认的System.String可选属性。 它可以设置为任何完全限定的类型,该类型要么是项,要么是可以通过 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元素具有Type属性为Fragment或Method,则会自动为每个参数创建属性。 否则,必须在任务源代码中显式声明属性,并且必须完全匹配其参数定义。
调试内联任务
MSBuild 生成内联任务的源文件,并将输出以 GUID 文件名的形式写入位于临时文件夹 AppData\Local\Temp\MSBuildTemp 中的文本文件。 输出通常被删除,但要保留此输出文件,可以将环境变量 MSBUILDLOGCODETASKFACTORYOUTPUT 设置为 1。
示例 1
以下内联任务将指定文件中所有出现的令牌替换为指定的值。
<Project>
<UsingTask TaskName="TokenReplace" TaskFactory="RoslynCodeTaskFactory" 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="Target.config" Token="$MyToken$" Replacement="MyValue"/>
</Target>
</Project>
示例 2
以下内联任务生成序列化输出。 此示例演示如何使用输出参数和引用。
<Project>
<PropertyGroup>
<RoslynCodeTaskFactoryAssembly Condition="$(RoslynCodeTaskFactoryAssembly) == ''">$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll</RoslynCodeTaskFactoryAssembly>
</PropertyGroup>
<UsingTask
TaskName="MyInlineTask"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(RoslynCodeTaskFactoryAssembly)">
<ParameterGroup>
<Input ParameterType="System.String" Required="true" />
<Output ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Text.Json" /> <!-- Reference an assembly -->
<Using Namespace="System.Text.Json" /> <!-- Use a namespace -->
<Code Type="Fragment" Language="cs">
<![CDATA[
Output = JsonSerializer.Serialize(new { Message = Input });
]]>
</Code>
</Task>
</UsingTask>
<Target Name="RunInlineTask">
<MyInlineTask Input="Hello, Roslyn!" >
<Output TaskParameter="Output" PropertyName="SerializedOutput" />
</MyInlineTask>
<Message Text="Serialized Output: $(SerializedOutput)" />
</Target>
</Project>