生成过程中的代码生成

文本转换可以作为 Visual Studio 解决方案的生成进程的一部分调用。 有一些专用于文本转换的生成任务。 T4 生成任务运行设计时文本模板,它们还编译运行时(已预处理的)文本模板。

根据你使用的引擎,生成任务可完成的操作之间是有一些差异的。 在 Visual Studio 中生成解决方案时,如果设置了 hostspecific="true" 特性,则文本模板可访问 Visual Studio API (EnvDTE)。 但当你从命令行生成解决方案时,或者当你通过 Visual Studio 启动服务器生成时,此操作无效。 在这些情况下,生成由 MSBuild 执行,并且使用不同的 T4 主机。

这意味着当你在 MSBuild 中生成文本模板时,你无法以相同的方式访问项目文件名等内容。 但是,你可以使用生成参数将环境信息传递到文本模板和指令处理器。

配置计算机

若要在开发计算机上启用生成任务,请安装 Visual Studio 的建模 SDK

如果生成服务器在未安装 Visual Studio 的计算机上运行,则将下列文件从你的开发计算机复制到生成计算机。 用最新的版本号替换‘*’。

  • $(ProgramFiles)\MSBuild\Microsoft\VisualStudio\v*.0\TextTemplating

    • Microsoft.VisualStudio.TextTemplating.Sdk.Host.*.0.dll

    • Microsoft.TextTemplating.Build.Tasks.dll

    • Microsoft.TextTemplating.targets

  • $(ProgramFiles)\Microsoft Visual Studio *.0\VSSDK\VisualStudioIntegration\Common\Assemblies\v4.0

    • Microsoft.VisualStudio.TextTemplating.*.0.dll

    • Microsoft.VisualStudio.TextTemplating.Interfaces.*.0.dll(多个文件)

    • Microsoft.VisualStudio.TextTemplating.VSHost.*.0.dll

  • $(ProgramFiles)\Microsoft Visual Studio *.0\Common7\IDE\PublicAssemblies\

    • Microsoft.VisualStudio.TextTemplating.Modeling.*.0.dll

编辑项目文件

你将必须编辑你的项目文件才能在 MSBuild 中配置部分功能。

在解决方案资源管理器中,从你的项目上下文菜单中选择**“卸载”**。 这允许你在 XML 编辑器中编辑 .csproj 或 .vbproj 文件。

完成编辑后,请选择**“重新加载”**。

导入文本转换目标

在 .vbproj 或 .csproj 文件中,查找与下类似的行:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

- 或 -

<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />

在该行之后插入文本模板化导入:

<!-- Optionally make the import portable across VS versions -->
  <PropertyGroup>
    <!-- Get the Visual Studio version – defaults to 10: -->
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
    <!-- Keep the next element all on one line: -->
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
  </PropertyGroup>


<!-- This is the important line: -->
  <Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" />

在生成中转换模板

有一些属性你可插入项目文件以控制转换任务:

  • 在每次生成开始时运行转换任务:

    <PropertyGroup>
        <TransformOnBuild>true</TransformOnBuild>
    </PropertyGroup>
    
  • 例如,覆盖只读文件,因为不会签出这些文件:

    <PropertyGroup>
        <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
    </PropertyGroup>
    
  • 每次转换所有模板:

    <PropertyGroup>
        <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
    </PropertyGroup>
    

    默认情况下,T4 MSBuild 任务将重新生成一个输出文件,前提是它早于其模板文件,或早于所有已包含的文件,或早于之前已由模板读取或已由其使用的指令处理器读取的所有文件。 请注意,这是一个依赖项测试,其功能比 Visual Studio 中的“转换所有模板”命令使用的测试强大得多,后者仅比较模板和输出文件的日期。

若要在项目中仅执行文本转换,请调用 TransformAll 任务:

msbuild myProject.csproj /t:TransformAll

转换特定文本模板:

msbuild myProject.csproj /t:Transform /p:TransformFile="Template1.tt"

你可以在 TransformFile 中使用通配符:

msbuild dsl.csproj /t:Transform /p:TransformFile="GeneratedCode\**\*.tt"

源代码管理

没有针对源代码管理系统的特定内置集成。 但是,你可以添加自己的扩展,例如,用于签出和签入生成的文件。默认情况下,文本转换任务将避免覆盖标记为只读的文件,因此当遇到此类文件时,将在 Visual Studio 错误列表中记录错误,并且任务将失败。

若要指定应覆盖只读文件,请插入此属性:

<OverwriteReadOnlyOuputFiles>true</OverwriteReadOnlyOuputFiles>

除非自定义后续处理步骤,否则会在覆盖文件时,在错误列表中记录相应的警告。

自定义生成过程

文本转换发生在生成过程中的其他任务之前。 你可以通过设置 $(BeforeTransform) 属性和 $(AfterTransform) 属性来定义在转换前后调用的任务:

<PropertyGroup>
    <BeforeTransform>CustomPreTransform</BeforeTransform>
    <AfterTransform>CustomPostTransform</AfterTransform>
  </PropertyGroup>
  <Target Name="CustomPreTransform">
    <Message Text="In CustomPreTransform..." Importance="High" />
  </Target>
  <Target Name="CustomPostTransform">
    <Message Text="In CustomPostTransform..." Importance="High" />
  </Target>

在 AfterTransform 中,你可以引用文件列表:

  • GeneratedFiles - 由过程写入的文件的列表。 对于覆盖现有只读文件的文件,%(GeneratedFiles.ReadOnlyFileOverwritten) 为 true。 可将这些文件签出源代码管理。

  • NonGeneratedFiles - 未覆盖的只读文件的列表。

例如,定义任务以签出 GeneratedFiles。

OutputFilePath 和 OutputFileName

这些属性仅由 MSBuild 使用。 它们不影响 Visual Studio 中的代码生成。 它们将生成的输出文件重定向到不同的文件夹或文件。 目标文件夹必须已存在。

<ItemGroup>
  <None Include="MyTemplate.tt">
    <Generator>TextTemplatingFileGenerator</Generator>
    <OutputFilePath>MyFolder</OutputFilePath>
    <LastGenOutput>MyTemplate.cs</LastGenOutput>
  </None>
</ItemGroup>

要重定向到的一个有用的文件夹是 $(IntermediateOutputPath).

如果指定和输出文件名,则它将优先于在模板的输出指令中指定的扩展。

<ItemGroup>
  <None Include="MyTemplate.tt">
    <Generator>TextTemplatingFileGenerator</Generator>
    <OutputFileName>MyOutputFileName.cs</OutputFileName>
    <LastGenOutput>MyTemplate.cs</LastGenOutput>
  </None>
</ItemGroup>

如果你还要使用“全部转换”命令或运行单个文件生成器来转换 VS 内的模板,则不建议指定 OutputFileName 或 OutputFilePath。 你最终将得到不同的文件路径,具体取决于你触发转换的方式。 这可能很容易让人产生混淆。

添加引用和包括路径

主机有一个默认的路径集,主机将在这些路径中搜索模板中引用的程序集。 添加到此路径集:

<ItemGroup>
    <T4ReferencePath Include="$(VsIdePath)PublicAssemblies\" />
    <!-- Add more T4ReferencePath items here -->
</ItemGroup>

若要设置将在其中搜索包含文件的文件夹,请提供以分号分隔的列表。 通常将添加到现有文件夹列表。

<PropertyGroup>
    <IncludeFolders>
$(IncludeFolders);$(MSBuildProjectDirectory)\Include;AnotherFolder;And\Another</IncludeFolders>
</PropertyGroup>

将生成上下文数据传递到模板

你可以在项目文件中设置参数值。 例如,你可以传递生成属性环境变量

<ItemGroup>
  <T4ParameterValues Include="ProjectFolder">
    <Value>$(ProjectDir)</Value>
    <Visible>false</Visible>
  </T4ParameterValues>
</ItemGroup>

在文本模板的 Template 指令中设置 hostspecific。 使用 parameter 指令获取值:

<#@template language="c#" hostspecific="true"#>
<#@ parameter type="System.String" name="ProjectFolder" #>
The project folder is: <#= ProjectFolder #>

在指令处理器中,你可以调用 ResolveParameterValue

string value = Host.ResolveParameterValue("-", "-", "parameterName");
Dim value = Host.ResolveParameterValue("-", "-", "parameterName")

备注

仅当你使用 MSBuild 时,ResolveParameterValue 才会通过 T4ParameterValues 获取数据。当你使用 Visual Studio 转换模板时,参数将具有默认值。

在 Assembly 和 Include 指令中使用项目属性

Visual Studio 宏(如 $(SolutionDir)在 MSBuild 中不起作用。 你可以改用项目属性。

编辑 .csproj 或 .vbproj 文件以定义项目属性。 此示例定义一个名为 myLibFolder 的属性:

<!-- Define a project property, myLibFolder: -->
<PropertyGroup>
    <myLibFolder>$(MSBuildProjectDirectory)\..\libs</myLibFolder>
</PropertyGroup>

<!-- Tell the MSBuild T4 task to make the property available: -->
<ItemGroup>
    <T4ParameterValues Include="myLibFolder">
      <Value>$(myLibFolder)</Value>
    </T4ParameterValues>
  </ItemGroup>

现在,你可以在 Assembly 和 Include 指令中使用项目属性:

<#@ assembly name="$(myLibFolder)\MyLib.dll" #>
<#@ include file="$(myLibFolder)\MyIncludeFile.t4" #>

这些指令在 MSBuild 和 Visual Studio 主机中都通过 T4parameterValues 获取值。

问题解答

为什么我需要在生成服务器中转换模板? 我已在签入我的代码前在 Visual Studio 中转换了模板。

如果你更新一个包含的文件或模板读取的其他文件,则 Visual Studio 将不会自动转换相应文件。 作为生成的一部分的模板转换操作将确保所有内容保持最新状态。

还有哪些其他选项适用于转换文本模板?

阅读详细信息

在 T4 MSbuild 模板(位于 $(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets)提供了有用的指南

编写 T4 文本模板

Visual Studio 可视化和建模 SDK

Oleg Sych:了解 T4:MSBuild 集成