property-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MSBuild Property Patterns

MSBuild 属性模式

Canonical property definition and manipulation patterns from the MSBuild repository.
来自MSBuild仓库的标准属性定义与操作模式。

Conditional Defaults — The Foundational Pattern

条件默认值 —— 基础模式

Set a property only if not already set, allowing callers to override:
xml
<PropertyGroup>
  <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
  <Platform Condition="'$(Platform)' == ''">AnyCPU</Platform>
  <BuildInParallel Condition="'$(BuildInParallel)' == ''">true</BuildInParallel>
</PropertyGroup>
仅在属性未被设置时进行设置,允许调用方重写:
xml
<PropertyGroup>
  <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
  <Platform Condition="'$(Platform)' == ''">AnyCPU</Platform>
  <BuildInParallel Condition="'$(BuildInParallel)' == ''">true</BuildInParallel>
</PropertyGroup>

Rules

规则

  • Always quote both sides:
    '$(Prop)' == ''
  • In
    .props
    : creates overridable defaults. In
    .targets
    : creates fallbacks.
  • Properties without the condition cannot be overridden by earlier imports.
  • 始终为条件两边添加引号:
    '$(Prop)' == ''
  • .props
    中:创建可重写的默认值。在
    .targets
    中:创建回退值。
  • 不带条件的属性无法被更早的导入项重写

Nested Conditional Groups

嵌套条件组

Group related properties under a shared condition:
xml
<PropertyGroup Condition="$(TargetFramework.StartsWith('net4'))">
  <DefineConstants>$(DefineConstants);FEATURE_APARTMENT_STATE</DefineConstants>
  <DefineConstants>$(DefineConstants);FEATURE_APM</DefineConstants>
  <FeatureAppDomain>true</FeatureAppDomain>
</PropertyGroup>

<PropertyGroup Condition="'$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))' == '.NETCoreApp'">
  <NetCoreBuild>true</NetCoreBuild>
  <DefineConstants>$(DefineConstants);RUNTIME_TYPE_NETCORE</DefineConstants>
</PropertyGroup>
Use the outer
Condition
on
PropertyGroup
to avoid repeating the same condition on every property.
Warning:
$(TargetFramework)
is empty in
.props
files for single-targeting projects until the project body is evaluated. Place
TargetFramework
-conditioned property groups in
.targets
files (or the project file itself), where the value is always available.
将相关属性归到同一个共享条件下:
xml
<PropertyGroup Condition="$(TargetFramework.StartsWith('net4'))">
  <DefineConstants>$(DefineConstants);FEATURE_APARTMENT_STATE</DefineConstants>
  <DefineConstants>$(DefineConstants);FEATURE_APM</DefineConstants>
  <FeatureAppDomain>true</FeatureAppDomain>
</PropertyGroup>

<PropertyGroup Condition="'$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))' == '.NETCoreApp'">
  <NetCoreBuild>true</NetCoreBuild>
  <DefineConstants>$(DefineConstants);RUNTIME_TYPE_NETCORE</DefineConstants>
</PropertyGroup>
PropertyGroup
上使用外层
Condition
,避免在每个属性上重复相同的条件。
警告: 在单目标项目的
.props
文件中,
$(TargetFramework)
在项目主体被求值前为空。将带有
TargetFramework
条件的属性组放在
.targets
文件(或项目文件本身)中,确保值始终可用。

Composition — Semicolon Concatenation

组合 —— 分号拼接

Properties that hold lists use semicolons. Always include the existing value when appending:
xml
<PropertyGroup>
  <DefineConstants>$(DefineConstants);MY_FEATURE</DefineConstants>
  <NoWarn>$(NoWarn);NU5131;IDE0005</NoWarn>
  <LibraryTargetFrameworks>$(FullFrameworkTFM);$(LatestDotNetCoreForMSBuild);netstandard2.0</LibraryTargetFrameworks>
</PropertyGroup>
存储列表的属性使用分号。追加时始终包含现有值:
xml
<PropertyGroup>
  <DefineConstants>$(DefineConstants);MY_FEATURE</DefineConstants>
  <NoWarn>$(NoWarn);NU5131;IDE0005</NoWarn>
  <LibraryTargetFrameworks>$(FullFrameworkTFM);$(LatestDotNetCoreForMSBuild);netstandard2.0</LibraryTargetFrameworks>
</PropertyGroup>

Path Normalization and Trailing Slashes

路径规范化与尾斜杠处理

xml
<!-- Ensure trailing slash on directories -->
<PropertyGroup>
  <OutDir Condition="'$(OutDir)' != '' and !HasTrailingSlash('$(OutDir)')">$(OutDir)\</OutDir>
</PropertyGroup>

<!-- Normalize paths for cross-platform -->
<PropertyGroup>
  <TargetRefPath>$([MSBuild]::NormalizePath('$(TargetDir)', 'ref', '$(TargetFileName)'))</TargetRefPath>
</PropertyGroup>

<!-- Make relative path absolute -->
<PropertyGroup>
  <MSBuildProjectExtensionsPath
      Condition="'$([System.IO.Path]::IsPathRooted('$(MSBuildProjectExtensionsPath)'))' == 'false'">
    $([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))
  </MSBuildProjectExtensionsPath>
</PropertyGroup>
xml
<!-- 确保目录带有尾斜杠 -->
<PropertyGroup>
  <OutDir Condition="'$(OutDir)' != '' and !HasTrailingSlash('$(OutDir)')">$(OutDir)\</OutDir>
</PropertyGroup>

<!-- 规范化路径以支持跨平台 -->
<PropertyGroup>
  <TargetRefPath>$([MSBuild]::NormalizePath('$(TargetDir)', 'ref', '$(TargetFileName)'))</TargetRefPath>
</PropertyGroup>

<!-- 将相对路径转为绝对路径 -->
<PropertyGroup>
  <MSBuildProjectExtensionsPath
      Condition="'$([System.IO.Path]::IsPathRooted('$(MSBuildProjectExtensionsPath)'))' == 'false'">
    $([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))
  </MSBuildProjectExtensionsPath>
</PropertyGroup>

Preferred path functions

推荐的路径函数

FunctionPurpose
$([MSBuild]::NormalizePath(...))
Combine and normalize (cross-platform)
$([System.IO.Path]::Combine(...))
Combine path segments
$([System.IO.Path]::IsPathRooted(...))
Check if absolute
HasTrailingSlash(...)
Check for trailing slash
$([MSBuild]::GetDirectoryNameOfFileAbove(...))
Walk up directory tree
$(MSBuildThisFileDirectory)
Directory of current file
函数用途
$([MSBuild]::NormalizePath(...))
组合并规范化路径(跨平台)
$([System.IO.Path]::Combine(...))
组合路径段
$([System.IO.Path]::IsPathRooted(...))
检查是否为绝对路径
HasTrailingSlash(...)
检查是否带有尾斜杠
$([MSBuild]::GetDirectoryNameOfFileAbove(...))
遍历上级目录树
$(MSBuildThisFileDirectory)
当前文件所在目录

Target Framework Detection Helpers

目标框架(TFM)检测辅助工具

xml
<!-- Get TFM identifier -->
<PropertyGroup Condition="'$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))' == '.NETCoreApp'">
  <NetCoreBuild>true</NetCoreBuild>
</PropertyGroup>

<!-- Check TFM compatibility -->
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net472'))">
  <UseFrozenVersions>true</UseFrozenVersions>
</PropertyGroup>

<!-- OS detection -->
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('windows'))">
  <DefineConstants>$(DefineConstants);TEST_ISWINDOWS</DefineConstants>
</PropertyGroup>
xml
<!-- 获取TFM标识符 -->
<PropertyGroup Condition="'$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))' == '.NETCoreApp'">
  <NetCoreBuild>true</NetCoreBuild>
</PropertyGroup>

<!-- 检查TFM兼容性 -->
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net472'))">
  <UseFrozenVersions>true</UseFrozenVersions>
</PropertyGroup>

<!-- 操作系统检测 -->
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('windows'))">
  <DefineConstants>$(DefineConstants);TEST_ISWINDOWS</DefineConstants>
</PropertyGroup>

Guard Properties

防护属性

Mark that a file has been imported to prevent double-imports:
xml
<!-- At the end of MySDK.props -->
<PropertyGroup>
  <MySDKPropsImported>true</MySDKPropsImported>
</PropertyGroup>

<!-- At the top of MySDK.targets -->
<Import Project="MySDK.props" Condition="'$(MySDKPropsImported)' != 'true'" />
标记文件已被导入,防止重复导入:
xml
<!-- 在MySDK.props末尾 -->
<PropertyGroup>
  <MySDKPropsImported>true</MySDKPropsImported>
</PropertyGroup>

<!-- 在MySDK.targets开头 -->
<Import Project="MySDK.props" Condition="'$(MySDKPropsImported)' != 'true'" />

Feature Gating by MSBuild Version

按MSBuild版本启用功能

xml
<PropertyGroup Condition="$([MSBuild]::AreFeaturesEnabled('17.10'))">
  <UseNewBehavior>true</UseNewBehavior>
</PropertyGroup>
xml
<PropertyGroup Condition="$([MSBuild]::AreFeaturesEnabled('17.10'))">
  <UseNewBehavior>true</UseNewBehavior>
</PropertyGroup>

Fallback Chains

回退链

Set via primary source first, then fall back:
xml
<PropertyGroup>
  <TlbExpPath>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToDotNetFrameworkSdkFile('tlbexp.exe'))</TlbExpPath>
  <TlbExpPath Condition="'$(TlbExpPath)' == ''">$(_NetFxToolsDir)TlbExp.exe</TlbExpPath>
</PropertyGroup>
优先通过主源设置,然后回退:
xml
<PropertyGroup>
  <TlbExpPath>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToDotNetFrameworkSdkFile('tlbexp.exe'))</TlbExpPath>
  <TlbExpPath Condition="'$(TlbExpPath)' == ''">$(_NetFxToolsDir)TlbExp.exe</TlbExpPath>
</PropertyGroup>

Last Write Wins — Evaluation Order

最后写入获胜 —— 求值顺序

MSBuild evaluates properties top-to-bottom. The last assignment wins:
xml
<!-- File 1 (imported first) -->
<MyProp>value1</MyProp>        <!-- set to value1 -->
<!-- File 2 (imported second) -->
<MyProp>value2</MyProp>        <!-- overwritten to value2 -->
<!-- File 3 (imported third) -->
<MyProp Condition="'$(MyProp)' == ''">value3</MyProp>  <!-- NOT set — already value2 -->
Properties in
.targets
(imported late) override properties in
.props
(imported early) and the project file.
MSBuild从上到下求值属性,最后一次赋值生效:
xml
<!-- 文件1(先导入) -->
<MyProp>value1</MyProp>        <!-- 设置为value1 -->
<!-- 文件2(后导入) -->
<MyProp>value2</MyProp>        <!-- 被覆盖为value2 -->
<!-- 文件3(最后导入) -->
<MyProp Condition="'$(MyProp)' == ''">value3</MyProp>  <!-- 不会设置 —— 已经是value2 -->
.targets
中的属性(晚导入)会覆盖
.props
(早导入)和项目文件中的属性。

Common Pitfalls

常见陷阱

  • Unquoted conditions (
    $(X)==true
    ) fail when the property is empty. Always quote both sides.
  • Overwriting DefineConstants (
    <DefineConstants>MY_CONST</DefineConstants>
    ) drops all prior constants. Always append with
    $(DefineConstants);
    .
  • Hardcoded absolute paths break portability. Use
    $(MSBuildThisFileDirectory)
    or
    $([MSBuild]::NormalizePath(...))
    .
  • Missing
    Condition
    on defaults
    makes properties non-overridable. Add
    Condition="'$(Prop)' == ''"
    for values meant to be defaults.
  • 未加引号的条件
    $(X)==true
    )在属性为空时失效。始终为两边添加引号。
  • 覆盖DefineConstants
    <DefineConstants>MY_CONST</DefineConstants>
    )会丢失之前所有的常量。始终使用
    $(DefineConstants);
    进行追加。
  • 硬编码绝对路径会破坏可移植性。使用
    $(MSBuildThisFileDirectory)
    $([MSBuild]::NormalizePath(...))
  • **默认值缺少
    Condition
    **会导致属性无法被重写。对于作为默认值的属性,添加
    Condition="'$(Prop)' == ''"