property-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMSBuild 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 : creates overridable defaults. In
.props: creates fallbacks..targets - 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 on to avoid repeating the same condition on every property.
ConditionPropertyGroupWarning:is empty in$(TargetFramework)files for single-targeting projects until the project body is evaluated. Place.props-conditioned property groups inTargetFrameworkfiles (or the project file itself), where the value is always available..targets
将相关属性归到同一个共享条件下:
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>在上使用外层,避免在每个属性上重复相同的条件。
PropertyGroupCondition警告: 在单目标项目的文件中,.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
推荐的路径函数
| Function | Purpose |
|---|---|
| Combine and normalize (cross-platform) |
| Combine path segments |
| Check if absolute |
| Check for trailing slash |
| Walk up directory tree |
| Directory of current file |
| 函数 | 用途 |
|---|---|
| 组合并规范化路径(跨平台) |
| 组合路径段 |
| 检查是否为绝对路径 |
| 检查是否带有尾斜杠 |
| 遍历上级目录树 |
| 当前文件所在目录 |
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 (imported late) override properties in (imported early) and the project file.
.targets.propsMSBuild从上到下求值属性,最后一次赋值生效:
xml
<!-- 文件1(先导入) -->
<MyProp>value1</MyProp> <!-- 设置为value1 -->
<!-- 文件2(后导入) -->
<MyProp>value2</MyProp> <!-- 被覆盖为value2 -->
<!-- 文件3(最后导入) -->
<MyProp Condition="'$(MyProp)' == ''">value3</MyProp> <!-- 不会设置 —— 已经是value2 -->.targets.propsCommon Pitfalls
常见陷阱
- Unquoted conditions () fail when the property is empty. Always quote both sides.
$(X)==true - Overwriting DefineConstants () drops all prior constants. Always append with
<DefineConstants>MY_CONST</DefineConstants>.$(DefineConstants); - Hardcoded absolute paths break portability. Use or
$(MSBuildThisFileDirectory).$([MSBuild]::NormalizePath(...)) - Missing on defaults makes properties non-overridable. Add
Conditionfor values meant to be defaults.Condition="'$(Prop)' == ''"
- 未加引号的条件()在属性为空时失效。始终为两边添加引号。
$(X)==true - 覆盖DefineConstants()会丢失之前所有的常量。始终使用
<DefineConstants>MY_CONST</DefineConstants>进行追加。$(DefineConstants); - 硬编码绝对路径会破坏可移植性。使用或
$(MSBuildThisFileDirectory)。$([MSBuild]::NormalizePath(...)) - **默认值缺少**会导致属性无法被重写。对于作为默认值的属性,添加
Condition。Condition="'$(Prop)' == ''"