自定义生成Customize your build

使用标准生成进程(导入 Microsoft.Common.props 和 Microsoft.Common.targets)的 MSBuild 项目有多个可用于自定义生成过程的扩展性挂钩 。MSBuild projects that use the standard build process (importing Microsoft.Common.props and Microsoft.Common.targets) have several extensibility hooks that you can use to customize your build process.

向项目的命令行 MSBuild 调用添加参数Add arguments to command-line MSBuild invocations for your project

源目录中或之上的 Directory.Build.rsp 文件将应用到项目的命令行生成 。A Directory.Build.rsp file in or above your source directory will be applied to command-line builds of your project. 有关详细信息,请参阅 MSBuild 响应文件For details, see MSBuild response files.

Directory.Build.props 和 Directory.Build.targetsDirectory.Build.props and Directory.Build.targets

在 MSBuild 15 版之前,如果要向解决方案中的项目提供新的自定义属性,必须手动向解决方案中的每个项目文件添加一个针对该属性的引用。Prior to MSBuild version 15, if you wanted to provide a new, custom property to projects in your solution, you had to manually add a reference to that property to every project file in the solution. 另外,还必须在 .props 文件中定义属性,然后在解决方案的每个项目中显式导入该 .props 文件。Or, you had to define the property in a .props file and then explicitly import the .props file in every project in the solution, among other things.

但现在,通过在包含源的根文件夹的名为 Directory.Build.props 的单个文件中定义一个新属性,只需一步即可向每个项目添加该属性。However, now you can add a new property to every project in one step by defining it in a single file called Directory.Build.props in the root folder that contains your source. 在 MSBuild 运行时,Microsoft.Common.props 会搜索 Directory.Build.props 文件的目录结构(Microsoft.Common.targets 将查找 Directory.Build.targets )。When MSBuild runs, Microsoft.Common.props searches your directory structure for the Directory.Build.props file (and Microsoft.Common.targets looks for Directory.Build.targets). 如果找到,就会导入该属性。If it finds one, it imports the property. Directory.Build.props 是用户定义文件,对目录下的项目提供自定义选项。Directory.Build.props is a user-defined file that provides customizations to projects under a directory.

Note

基于 Linux 的文件系统区分大小写。Linux-based file systems are case-sensitive. 请确保 Directory.Build.props 文件名的大小写完全匹配,否则将不会在生成流程中检测到它。Make sure the casing of the Directory.Build.props filename matches exactly, or it won't be detected during the build process.

有关详细信息,请参阅此 GitHub 问题See this GitHub issue for more information.

Directory.Build.props 示例Directory.Build.props example

例如,如果想要使所有项目都可以访问新的 Roslyn /deterministic 功能(属性 $(Deterministic) 在 Roslyn CoreCompile 目标中公开了此功能),可以执行以下操作。For example, if you wanted to enable all of your projects to access the new Roslyn /deterministic feature (which is exposed in the Roslyn CoreCompile target by the property $(Deterministic)), you could do the following.

  1. 在存储库根目录中创建一个名为 Directory.Build.props 的新文件。Create a new file in the root of your repo called Directory.Build.props.

  2. 将以下 XML 添加到此文件。Add the following XML to the file.

    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. 运行 MSBuild。Run MSBuild. 项目现有的 Microsoft.Common.props 和 Microsoft.Common.targets 导入会找到该文件并将其导入。Your project’s existing imports of Microsoft.Common.props and Microsoft.Common.targets find the file and import it.

搜索范围Search scope

搜索 Directory.Build.props 文件时,MSBuild 将从项目位置 ($(MSBuildProjectFullPath)) 向上搜索目录结构,找到 Directory.Build.props 文件后停止。When searching for a Directory.Build.props file, MSBuild walks the directory structure upwards from your project location ($(MSBuildProjectFullPath)), stopping after it locates a Directory.Build.props file. 例如,如果 $(MSBuildProjectFullPath) 为 c:\users\username\code\test\case1 ,MSBuild 将从该位置开始搜索,然后向上搜索目录结构,直到找到 Directory.Build.props 文件,如以下目录结构中所示。For example, if your $(MSBuildProjectFullPath) was c:\users\username\code\test\case1, MSBuild would start searching there and then search the directory structure upward until it located a Directory.Build.props file, as in the following directory structure.

c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\

解决方案文件的位置与 Directory.Build.props 无关。The location of the solution file is irrelevant to Directory.Build.props.

导入顺序Import order

Directory.Build.props 很早便已导入 Microsoft.Common.props,因此它无法使用后来定义的属性 。Directory.Build.props is imported very early in Microsoft.Common.props, and properties defined later are unavailable to it. 因此,请避免引用尚未定义的属性(否则计算结果将为空)。So, avoid referring to properties that are not yet defined (and will evaluate to empty).

从 NuGet 包导入 .targets 文件后,会从 Microsoft.Common.targets 导入 Directory.Build.targets 。Directory.Build.targets is imported from Microsoft.Common.targets after importing .targets files from NuGet packages. 因此,它会重写大部分生成逻辑中定义的属性和目标,但有时候,可能需要在最终导入后自定义项目文件。So, it can override properties and targets defined in most of the build logic, but sometimes you may need to customize the project file after the final import.

用例:多级别合并Use case: multi-level merging

假设你具有此标准解决方案结构:Suppose you have this standard solution structure:

\
  MySolution.sln
  Directory.Build.props     (1)
  \src
    Directory.Build.props   (2-src)
    \Project1
    \Project2
  \test
    Directory.Build.props   (2-test)
    \Project1Tests
    \Project2Tests

则可能需要具有所有项目 (1) 的通用属性、src 项目 (2-src) 的通用属性,以及 test 项目 (2-test) 的通用属性。It might be desirable to have common properties for all projects (1), common properties for src projects (2-src), and common properties for test projects (2-test).

若要 MSBuild 正确地合并“内部”文件(2-src 和 2-test)和“外部”文件 (1),必须考虑到 MSBuild 找到 Directory.Build.props 文件后会立即停止进一步的扫描 。To make MSBuild correctly merge the "inner" files (2-src and 2-test) with the "outer" file (1), you must take into account that once MSBuild finds a Directory.Build.props file, it stops further scanning. 要继续扫描并合并到外部文件,请将此代码置于这两个内部文件中:To continue scanning and merge into the outer file, place this code into both inner files:

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

MSBuild 的常规方法汇总如下:A summary of MSBuild's general approach is as follows:

  • 对于任何给定的项目,MSBuild 在解决方案结构中向上查找第一个 Directory.Build.props ,将其与默认项合并,然后停止扫描For any given project, MSBuild finds the first Directory.Build.props upward in the solution structure, merges it with defaults, and stops scanning for more
  • 如果要找到并合并多个级别,则从“内部”文件 <Import...>(如上所示)“外部”文件If you want multiple levels to be found and merged, then <Import...> (shown above) the "outer" file from the "inner" file
  • 如果“外部”文件本身不会再导入其上的内容,则扫描在此处停止If the "outer" file does not itself also import something above it, then scanning stops there
  • 要控制扫描/合并过程,请使用 $(DirectoryBuildPropsPath)$(ImportDirectoryBuildProps)To control the scanning/merging process, use $(DirectoryBuildPropsPath) and $(ImportDirectoryBuildProps)

或再简单点:不能导入任何内容的第一个 Directory.Build.props 即为 MSBuild 停止的位置 。Or more simply: the first Directory.Build.props that doesn't import anything is where MSBuild stops.

选择将属性添加到 .props 文件或 .targets 文件Choose between adding properties to a .props or .targets file

MSBuild 依赖于导入顺序,属性(或 UsingTask 或目标)的最后一个定义是使用的定义。MSBuild is import-order dependent, and the last definition of a property (or a UsingTask or target) is the definition used.

使用显式导入时,可以随时从 .props 或 .targets 文件导入。When using explicit imports, you can import from a .props or .targets file at any point. 下面介绍广泛使用的约定:Here is the widely used convention:

  • .props 文件在导入顺序的早期导入。.props files are imported early in the import order.

  • .targets 文件在生成顺序的后期导入。.targets files are imported late in the build order.

此约定由 <Project Sdk="SdkName"> 导入强制执行(即,在文件的所有内容之前首先导入 Sdk.props ,然后在文件的所有内容之后最后导入 Sdk.targets )。This convention is enforced by <Project Sdk="SdkName"> imports (that is, the import of Sdk.props comes first, before all of the contents of the file, then Sdk.targets comes last, after all of the contents of the file).

在决定在何处放置属性后,使用以下通用原则:When deciding where to put the properties, use the following general guidelines:

  • 对于许多属性,在何处定义它们并不重要,因为它们不会被覆盖,只能在执行时读取。For many properties, it doesn't matter where they're defined, because they're not overwritten and will be read only at execution time.

  • 对于可能在单个项目中自定义的行为,请在 .props 文件中设置默认值。For behavior that might be customized in an individual project, set defaults in .props files.

  • 通过读取可能自定义属性的值,避免在 .props 文件中设置依赖属性,因为在 MSBuild 读取用户项目之前不会进行自定义。Avoid setting dependent properties in .props files by reading the value of a possibly customized property, because the customization won't happen until MSBuild reads the user's project.

  • 在 .targets 文件中设置依赖属性,因为它们将从单个项目中提取自定义项。Set dependent properties in .targets files, because they'll pick up customizations from individual projects.

  • 如果需要覆盖属性,请在所有用户项目自定义项生效后,在 .targets 文件中执行此操作。If you need to override properties, do it in a .targets file, after all user-project customizations have had a chance to take effect. 使用派生属性时务必小心;还可能需要覆盖派生属性。Be cautious when using derived properties; derived properties may need to be overridden as well.

  • 包括 .props 文件中的项目(以属性为条件)。Include items in .props files (conditioned on a property). 在任何项目之前都要考虑所有属性,因此可以提取用户项目属性自定义项,这使用户的项目有机会 RemoveUpdate 导入所引入的任何项目。All properties are considered before any item, so user-project property customizations get picked up, and this gives the user's project the opportunity to Remove or Update any item brought in by the import.

  • 定义 .targets 文件中的目标。Define targets in .targets files. 但是,如果 SDK 导入了 .targets 文件,请记住此方案使得覆盖目标更加困难,因为默认情况下用户的项目没有可以覆盖它的地方。However, if the .targets file is imported by an SDK, remember that this scenario makes overriding the target more difficult because the user's project doesn't have a place to override it by default.

  • 如果可能,宁可在评估时自定义属性,也不更改目标内的属性。If possible, prefer customizing properties at evaluation time over changing properties inside a target. 此原则可以更轻松地加载项目并了解正在执行的操作。This guideline makes it easier to load a project and understand what it's doing.

MSBuildProjectExtensionsPathMSBuildProjectExtensionsPath

默认情况下,Microsoft.Common.props 导入 $(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.props,Microsoft.Common.targets 导入 $(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.targetsBy default, Microsoft.Common.props imports $(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.props and Microsoft.Common.targets imports $(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.targets. MSBuildProjectExtensionsPath 的默认值是 $(BaseIntermediateOutputPath)obj/The default value of MSBuildProjectExtensionsPath is $(BaseIntermediateOutputPath), obj/. NuGet 用此机制来引用随包提供的生成逻辑,也就是说,在还原时,它会创建引用包内容的 {project}.nuget.g.props 文件。NuGet uses this mechanism to refer to build logic delivered with packages; that is, at restore time, it creates {project}.nuget.g.props files that refer to the package contents.

可以通过在 Directory.Build.props 中或者在导入 Microsoft.Common.props 前将属性 ImportProjectExtensionProps 设为 false 来禁用此扩展性机制 。You can disable this extensibility mechanism by setting the property ImportProjectExtensionProps to false in a Directory.Build.props or before importing Microsoft.Common.props.

Note

禁用 MSBuildProjectExtensionsPath 导入将阻止在 NuGet 包中提供的生成逻辑应用到你的项目。Disabling MSBuildProjectExtensionsPath imports will prevent build logic delivered in NuGet packages from applying to your project. 一些 NuGet 包需要生成逻辑来执行其功能,并且在禁用该功能时会呈现不可用。Some NuGet packages require build logic to perform their function and will be rendered useless when this is disabled.

.user 文件.user file

Microsoft.Common.CurrentVersion.targets 会导入 $(MSBuildProjectFullPath).user(如果存在),因此可以使用其他文件扩展名在你的项目旁创建一个文件 。Microsoft.Common.CurrentVersion.targets imports $(MSBuildProjectFullPath).user if it exists, so you can create a file next to your project with that additional extension. 对于计划签入源代码管理的长期更改,最好更改项目本身,以便将来的维护人员不必了解此扩展机制。For long-term changes you plan to check into source control, prefer changing the project itself, so that future maintainers do not have to know about this extension mechanism.

MSBuildExtensionsPath 和 MSBuildUserExtensionsPathMSBuildExtensionsPath and MSBuildUserExtensionsPath

Warning

如果使用这些扩展机制,则较难获取计算机上的可重复生成。Using these extension mechanisms makes it harder to get repeatable builds across machines. 尝试使用可以签入源代码管理系统并在基本代码的所有开发人员之间共享的配置。Try to use a configuration that can be checked into your source control system and shared among all developers of your codebase.

按照惯例,许多核心生成逻辑文件By convention, many core build logic files import

$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\{TargetFileName}\ImportBefore\*.targets

会在其内容前后各导入一次before their contents, and

$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\{TargetFileName}\ImportAfter\*.targets

afterward. 这一约定使已安装的 SDK 可以增强常见项目类型的生成逻辑。This convention allows installed SDKs to augment the build logic of common project types.

$(MSBuildUserExtensionsPath) 中搜索相同的目录结构,即按用户文件夹 %LOCALAPPDATA%\Microsoft\MSBuild 。The same directory structure is searched in $(MSBuildUserExtensionsPath), which is the per-user folder %LOCALAPPDATA%\Microsoft\MSBuild. 放置在该文件夹中的文件将被导入该用户凭据下运行的相应项目类型的所有生成。Files placed in that folder will be imported for all builds of the corresponding project type run under that user's credentials. 通过在模式 ImportUserLocationsByWildcardBefore{ImportingFileNameWithNoDots} 中设置以导入文件命名的属性,可以禁用用户扩展。You can disable the user extensions by setting properties named after the importing file in the pattern ImportUserLocationsByWildcardBefore{ImportingFileNameWithNoDots}. 例如,将 ImportUserLocationsByWildcardBeforeMicrosoftCommonProps 设置为 false 会阻止导入 $(MSBuildUserExtensionsPath)\$(MSBuildToolsVersion)\Imports\Microsoft.Common.props\ImportBefore\*For example, setting ImportUserLocationsByWildcardBeforeMicrosoftCommonProps to false would prevent importing $(MSBuildUserExtensionsPath)\$(MSBuildToolsVersion)\Imports\Microsoft.Common.props\ImportBefore\*.

自定义解决方案生成Customize the solution build

Important

以这种方式自定义解决方案生成将仅适用于带有 MSBuild.exe 的命令行生成 。Customizing the solution build in this way applies only to command-line builds with MSBuild.exe. 它不适用于 Visual Studio 中的生成 。It does not apply to builds inside Visual Studio.

当 MSBuild 生成解决方案文件时,它首先在内部转换为项目文件,然后再生成它。When MSBuild builds a solution file, it first translates it internally into a project file and then builds that. 已生成的项目文件在定义任何目标前导入 before.{solutionname}.sln.targets,在导入目标后导入 after.{solutionname}.sln.targets ,其中包括安装到 $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter 目录的目标。The generated project file imports before.{solutionname}.sln.targets before defining any targets and after.{solutionname}.sln.targets after importing targets, including targets installed to the $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore and $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter directories.

例如,可以在包含以下内容的名为 after.MyCustomizedSolution.sln.targets 的相同目录中创建文件,从而定义在生成 MyCustomizedSolution.sln 后写自定义日志消息的新目标 For example, you could define a new target to write a custom log message after building MyCustomizedSolution.sln by creating a file in the same directory named after.MyCustomizedSolution.sln.targets that contains

<Project>
 <Target Name="EmitCustomMessage" AfterTargets="Build">
   <Message Importance="High" Text="The solution has completed the Build target" />
 </Target>
</Project>

请参阅See also