directory-build-organization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Organizing Build Infrastructure with Directory.Build Files

使用Directory.Build文件组织构建基础设施

Directory.Build.props vs Directory.Build.targets

Directory.Build.props 与 Directory.Build.targets 的区别

Understanding which file to use is critical. They differ in when they are imported during evaluation:
Evaluation order:
Directory.Build.props → SDK .props → YourProject.csproj → SDK .targets → Directory.Build.targets
Use
.props
for
Use
.targets
for
Setting property defaultsCustom build targets
Common item definitionsLate-bound property overrides
Properties projects can overridePost-build steps
Assembly/package metadataConditional logic on final values
Analyzer PackageReferencesTargets that depend on SDK-defined properties
Rule of thumb: Properties and items go in
.props
. Custom targets and late-bound logic go in
.targets
.
Because
.props
is imported before the project file, the project can override any value set there. Because
.targets
is imported after everything, it gets the final say—but projects cannot override
.targets
values.
了解应该使用哪个文件至关重要。它们的区别在于评估期间的导入时机
评估顺序:
Directory.Build.props → SDK .props → YourProject.csproj → SDK .targets → Directory.Build.targets
适用于
.props
的场景
适用于
.targets
的场景
设置属性默认值自定义构建目标
通用项定义后期绑定属性重写
项目可重写的属性构建后步骤
程序集/包元数据基于最终值的条件逻辑
分析器 PackageReferences依赖于SDK定义属性的目标
经验法则: 属性和项放在
.props
中。自定义目标和后期绑定逻辑放在
.targets
中。
由于
.props
在项目文件之前导入,因此项目可以覆盖其中设置的任何值。由于
.targets
在所有内容之后导入,它拥有最终决定权——但项目无法覆盖
.targets
中的值。

⚠️ Critical: TargetFramework Availability in .props vs .targets

⚠️ 关键注意事项:.props 与 .targets 中的 TargetFramework 可用性

Property conditions on
$(TargetFramework)
in
.props
files silently fail for single-targeting projects
— the property is empty during
.props
evaluation. Move TFM-conditional properties to
.targets
instead. ItemGroup and Target conditions are not affected.
See targetframework-props-pitfall.md for the full explanation.
.props
文件中对
$(TargetFramework)
设置的属性条件在单目标项目中会静默失败
——在
.props
评估期间该属性为空。请将TFM相关的条件属性移至
.targets
中。ItemGroup和Target的条件不受影响。
有关完整说明,请参阅 targetframework-props-pitfall.md

Directory.Build.props

Directory.Build.props

Good candidates: language settings, assembly/package metadata, build warnings, code analysis, common analyzers.
xml
<Project>
  <PropertyGroup>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <Company>Contoso</Company>
    <Authors>Contoso Engineering</Authors>
  </PropertyGroup>
</Project>
Do NOT put here: project-specific TFMs, project-specific PackageReferences, targets/build logic, or properties depending on SDK-defined values (not available during
.props
evaluation).
适用场景:语言设置、程序集/包元数据、构建警告、代码分析、通用分析器。
xml
<Project>
  <PropertyGroup>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <Company>Contoso</Company>
    <Authors>Contoso Engineering</Authors>
  </PropertyGroup>
</Project>
请勿在此放置: 项目特定的TFM、项目特定的PackageReferences、目标/构建逻辑,或依赖于SDK定义值的属性(在
.props
评估期间不可用)。

Directory.Build.targets

Directory.Build.targets

Good candidates: custom build targets, late-bound property overrides (values depending on SDK properties), post-build validation.
xml
<Project>
  <Target Name="ValidateProjectSettings" BeforeTargets="Build">
    <Error Text="All libraries must target netstandard2.0 or higher"
           Condition="'$(OutputType)' == 'Library' AND '$(TargetFramework)' == 'net472'" />
  </Target>

  <PropertyGroup>
    <!-- DocumentationFile depends on OutputPath, which is set by the SDK -->
    <DocumentationFile Condition="'$(IsPackable)' == 'true'">$(OutputPath)$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>
</Project>
适用场景:自定义构建目标、后期绑定属性重写(依赖于SDK属性的值)、构建后验证。
xml
<Project>
  <Target Name="ValidateProjectSettings" BeforeTargets="Build">
    <Error Text="All libraries must target netstandard2.0 or higher"
           Condition="'$(OutputType)' == 'Library' AND '$(TargetFramework)' == 'net472'" />
  </Target>

  <PropertyGroup>
    <!-- DocumentationFile depends on OutputPath, which is set by the SDK -->
    <DocumentationFile Condition="'$(IsPackable)' == 'true'">$(OutputPath)$(AssemblyName).xml</DocumentationFile>
  </PropertyGroup>
</Project>

Directory.Packages.props (Central Package Management)

Directory.Packages.props(集中包管理)

Central Package Management (CPM) provides a single source of truth for all NuGet package versions. See https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management for details.
Enable CPM in
Directory.Packages.props
at the repo root:
xml
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>

  <ItemGroup>
    <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
    <PackageVersion Include="xunit" Version="2.9.0" />
    <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
  </ItemGroup>

  <ItemGroup>
    <!-- GlobalPackageReference applies to ALL projects — great for analyzers -->
    <GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
    <GlobalPackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" />
  </ItemGroup>
</Project>
集中包管理(CPM)为所有NuGet包版本提供单一事实来源。有关详细信息,请参阅 https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management
在代码库根目录的
Directory.Packages.props
中启用CPM:
xml
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>

  <ItemGroup>
    <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
    <PackageVersion Include="xunit" Version="2.9.0" />
    <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
  </ItemGroup>

  <ItemGroup>
    <!-- GlobalPackageReference applies to ALL projects — great for analyzers -->
    <GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
    <GlobalPackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" />
  </ItemGroup>
</Project>

Directory.Build.rsp

Directory.Build.rsp

Contains default MSBuild CLI arguments applied to all builds under the directory tree.
Example
Directory.Build.rsp
:
/maxcpucount
/nodeReuse:false
/consoleLoggerParameters:Summary;ForceNoAlign
/warnAsMessage:MSB3277
  • Works with both
    msbuild
    and
    dotnet
    CLI in modern .NET versions
  • Great for enforcing consistent CI and local build flags
  • Each argument goes on its own line
包含应用于目录树下所有构建的默认MSBuild CLI参数。
Directory.Build.rsp
示例:
/maxcpucount
/nodeReuse:false
/consoleLoggerParameters:Summary;ForceNoAlign
/warnAsMessage:MSB3277
  • 在现代.NET版本中可与
    msbuild
    dotnet
    CLI配合使用
  • 非常适合强制执行一致的CI和本地构建标志
  • 每个参数单独占一行

Multi-level Directory.Build Files

多层级Directory.Build文件

MSBuild only auto-imports the first
Directory.Build.props
(or
.targets
) it finds walking up from the project directory. To chain multiple levels, explicitly import the parent at the top of the inner file. See multi-level-examples for full file examples.
xml
<Project>
  <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))"
         Condition="Exists('$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))')" />

  <!-- Inner-level overrides go here -->
</Project>
Example layout:
repo/
  Directory.Build.props          ← repo-wide (lang version, company info, analyzers)
  Directory.Build.targets        ← repo-wide targets
  Directory.Packages.props       ← central package versions
  src/
    Directory.Build.props        ← src-specific (imports repo-level, sets IsPackable=true)
  test/
    Directory.Build.props        ← test-specific (imports repo-level, sets IsPackable=false, adds test packages)
MSBuild在从项目目录向上遍历的过程中只会自动导入第一个找到的
Directory.Build.props
(或
.targets
)。要链接多个层级,请在内部文件的顶部显式导入父文件。有关完整文件示例,请参阅 multi-level-examples
xml
<Project>
  <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))"
         Condition="Exists('$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))')" />

  <!-- Inner-level overrides go here -->
</Project>
示例布局:
repo/
  Directory.Build.props          ← 代码库全局设置(语言版本、公司信息、分析器)
  Directory.Build.targets        ← 代码库全局目标
  Directory.Packages.props       ← 集中包版本
  src/
    Directory.Build.props        ← 源码目录特定设置(导入代码库级设置,设置IsPackable=true)
  test/
    Directory.Build.props        ← 测试目录特定设置(导入代码库级设置,设置IsPackable=false,添加测试包)

Artifact Output Layout (.NET 8+)

产物输出布局(.NET 8+)

Set
<ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath>
in
Directory.Build.props
to automatically produce project-name-separated
bin/
,
obj/
, and
publish/
directories under a single
artifacts/
folder, avoiding bin/obj clashes by default. See common-patterns for the directory layout and additional patterns (conditional settings by project type, post-pack validation).
Directory.Build.props
中设置
<ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath>
,可自动在单个
artifacts/
文件夹下生成按项目名称分隔的
bin/
obj/
publish/
目录,默认避免bin/obj目录冲突。有关目录布局和其他模式(按项目类型设置条件、打包后验证),请参阅 common-patterns

Workflow: Organizing Build Infrastructure

工作流:组织构建基础设施

  1. Audit all
    .csproj
    files
    — Catalog every
    <PropertyGroup>
    ,
    <ItemGroup>
    , and custom
    <Target>
    across the solution. Note which settings repeat and which are project-specific.
  2. Create root
    Directory.Build.props
    — Move shared property defaults (LangVersion, Nullable, TreatWarningsAsErrors, metadata) here. These are imported before the project file so projects can override them.
  3. Create root
    Directory.Build.targets
    — Move custom build targets, post-build validation, and any properties that depend on SDK-defined values (e.g.,
    OutputPath
    ,
    TargetFramework
    for single-targeting projects) here. These are imported after the SDK so all properties are available.
  4. Create
    Directory.Packages.props
    — Enable Central Package Management (
    ManagePackageVersionsCentrally
    ), list all
    PackageVersion
    entries, and remove
    Version=
    from
    PackageReference
    items in
    .csproj
    files.
  5. Set up multi-level hierarchy — Create inner
    Directory.Build.props
    files for
    src/
    and
    test/
    folders with distinct settings. Use
    GetPathOfFileAbove
    to chain to the parent.
  6. Simplify
    .csproj
    files
    — Remove all centralized properties, version attributes, and duplicated targets. Each project should only contain what is unique to it.
  7. Validate — Run
    dotnet restore && dotnet build
    and verify no regressions. Use
    dotnet msbuild -pp:output.xml
    to inspect the final merged view if needed.
  1. 审核所有
    .csproj
    文件
    —— 记录解决方案中所有的
    <PropertyGroup>
    <ItemGroup>
    和自定义
    <Target>
    。记录哪些设置是重复的,哪些是项目特定的。
  2. 创建根目录
    Directory.Build.props
    —— 将共享属性默认值(LangVersion、Nullable、TreatWarningsAsErrors、元数据)移至此处。这些设置会在项目文件之前导入,因此项目可以覆盖它们。
  3. 创建根目录
    Directory.Build.targets
    —— 将自定义构建目标、构建后验证以及所有依赖于SDK定义值的属性(例如
    OutputPath
    、单目标项目的
    TargetFramework
    )移至此处。这些设置会在SDK之后导入,因此所有属性都已可用。
  4. 创建
    Directory.Packages.props
    —— 启用集中包管理(
    ManagePackageVersionsCentrally
    ),列出所有
    PackageVersion
    条目,并从
    .csproj
    文件的
    PackageReference
    项中移除
    Version=
    属性。
  5. 设置多层级结构 —— 为
    src/
    test/
    文件夹创建内部
    Directory.Build.props
    文件,配置不同的设置。使用
    GetPathOfFileAbove
    链接到父文件。
  6. 简化
    .csproj
    文件
    —— 移除所有集中管理的属性、版本属性和重复的目标。每个项目应仅包含其特有的设置。
  7. 验证 —— 运行
    dotnet restore && dotnet build
    并验证没有回归问题。如有需要,可使用
    dotnet msbuild -pp:output.xml
    检查最终合并视图。

Troubleshooting

故障排除

ProblemCauseFix
Directory.Build.props
isn't picked up
File name casing wrong (exact match required on Linux/macOS)Verify exact casing:
Directory.Build.props
(capital D, B)
Properties from
.props
are ignored by projects
Project sets the same property after the importMove the property to
Directory.Build.targets
to set it after the project
Multi-level import doesn't workMissing
GetPathOfFileAbove
import in inner file
Add the
<Import>
element at the top of the inner file (see Multi-level section)
Properties using SDK values are empty in
.props
SDK properties aren't defined yet during
.props
evaluation
Move to
.targets
which is imported after the SDK
Directory.Packages.props
not found
File not at repo root or not named exactlyMust be named
Directory.Packages.props
and at or above the project directory
Property condition on
$(TargetFramework)
doesn't match in
.props
TargetFramework
isn't set yet for single-targeting projects during
.props
evaluation
Move property to
.targets
, or use ItemGroup/Target conditions instead (which evaluate late)
Diagnosis: Use the preprocessed project output to see all imports and final property values:
bash
dotnet msbuild -pp:output.xml MyProject.csproj
This expands all imports inline so you can see exactly where each property is set and what the final evaluated value is.
问题原因解决方法
Directory.Build.props
未被识别
文件名大小写错误(在Linux/macOS下需要完全匹配)验证文件名完全匹配:
Directory.Build.props
(首字母D、B大写)
.props
中的属性被项目忽略
项目在导入后设置了相同的属性将该属性移至
Directory.Build.targets
,使其在项目之后设置
多层级导入不生效内部文件中缺少
GetPathOfFileAbove
导入语句
在内部文件顶部添加
<Import>
元素(请参阅多层级部分)
.props
中使用SDK值的属性为空
.props
评估期间SDK属性尚未定义
移至
.targets
,因为它在SDK之后导入
Directory.Packages.props
未找到
文件不在代码库根目录或文件名不正确必须命名为
Directory.Packages.props
且位于项目目录或其上级目录
.props
中对
$(TargetFramework)
的条件不匹配
.props
评估期间单目标项目的
TargetFramework
尚未设置
将属性移至
.targets
,或改用ItemGroup/Target的条件(这些条件会延迟评估)
诊断方法: 使用预处理项目输出来查看所有导入和最终属性值:
bash
dotnet msbuild -pp:output.xml MyProject.csproj
此命令会将所有导入展开为内联内容,以便你准确查看每个属性的设置位置和最终评估值。