property-patterns — скилл для Claude Code: свойства MSBuild
Разбирает условия, дефолты и перезаписи в свойствах MSBuild по шаблонам из репозитория MSBuild. Пригодится, когда сборка берёт не то значение или падает на чужой ОС.
npx skills add dotnet/skills --skill property-patterns -a claude-codeЧто делает property-patterns
Скилл ведёт агента по каноническим приёмам работы со свойствами MSBuild. Он смотрит в проект и уже существующие общие файлы и ищет конкретные дефекты: незакавыченное условие, дефолт без Condition, затирание DefineConstants, абсолютный путь вместо переносимого, значение, которое перебил поздний импорт.
Внутри SKILL.md с блоками XML на каждый случай. Условные дефолты вида Condition="'$(Prop)' == ''", вложенные группы под общим условием, сцепка списков через точку с запятой ($(DefineConstants);MY_FEATURE), нормализация путей через $([MSBuild]::NormalizePath(...)) и $([System.IO.Path]::IsPathRooted(...)), guard-свойства против двойного импорта, гейтинг поведения по версии через $([MSBuild]::AreFeaturesEnabled('17.10')), разбор порядка вычисления по правилу последней записи.
Есть предупреждение, на котором спотыкаются чаще всего: в .props у проекта с одним таргетом $(TargetFramework) пустой, пока не вычислено тело проекта. Условия по TFM оттуда просто не срабатывают, и это повод перенести группу в .targets. За задачи без дефекта свойства скилл не берётся: выбор файла под таргет или перенос импортов без поломки отданы другому скиллу, directory-build-organization.
Какие задачи решает
- найти свойство, которое перебивает поздний импорт
- починить дефолт, который не применяется из-за пустого условия
- дописать
DefineConstants, не потеряв прежние константы - убрать из
.propsжёсткий абсолютный путь - перенести условные группы по TFM из
.propsв.targets
Примеры запросов агенту
- Почему в проекте Configuration всегда Debug? Проверь условия в
Directory.Build.propsи поправь дефолты. - Условие по
TargetFrameworkв.propsне срабатывает — разберись и перенеси группу куда надо. - Пройди по свойствам в наших общих файлах и найди, где затирается
DefineConstants.
Как установить property-patterns
1Через Skills CLI (Vercel)
npx skills add dotnet/skills --skill property-patterns -a claude-code2Вручную: положить файлы
~/.claude/skills/property-patterns/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/property-patterns/
1Через Skills CLI (Vercel)
npx skills add dotnet/skills --skill property-patterns -a codex2Вручную: положить файлы
.agents/skills/property-patterns/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add dotnet/skills --skill property-patterns -a cursor2Вручную: положить файлы
.cursor/skills/property-patterns/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install dotnet/skills property-patterns2Вручную: положить файлы
.github/skills/property-patterns/SKILL.md1Через Skills CLI (Vercel)
npx skills add dotnet/skills --skill property-patterns -a gemini-cli2Командой платформы
gemini skills install https://github.com/dotnet/skills --path plugins/dotnet-msbuild/skills/property-patternsВручную: положить файлы
.devin/skills/property-patterns/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add dotnet/skills --skill property-patterns -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Из того же набора101
Похожие
Рядом в теме «DevOps и инфраструктура»
Всё по теме «DevOps и инфраструктура» →Вопросы о property-patterns
Как установить property-patterns в Claude Code?
Выполните npx skills add dotnet/skills --skill property-patterns -a claude-code. Файл скилла появится по пути ~/.claude/skills/property-patterns/SKILL.md.
Что делает property-patterns?
Диагностирует и чинит дефекты свойств MSBuild: условия, дефолты, дописывание против перезаписи, проверки ОС и TFM, переносимые пути. Работает по проекту и по общим файлам вроде Directory.Build.*.
Почему условие по TargetFramework в .props не работает?
У проекта с одним таргетом $(TargetFramework) пустой в .props, пока не вычислено тело проекта. Поэтому такие группы ставят в .targets или в сам файл проекта.
Исходный текст
SKILL.md · MIT
---
name: property-patterns
description: "Diagnose and fix concrete MSBuild property defects in projects and existing shared-file hierarchies. USE FOR: conditions, defaults, append versus overwrite, last-write-wins values, OS/TFM checks, portable paths, normalization, and reviews centered on those property defects. Property defects in Directory.Build.* remain in scope, including overwritten values across parent/child imports and conditions that run before TargetFramework is set, even when the fix changes import order or moves a property group to .targets. DO NOT USE FOR: placement-only or import-only requests with no concrete property defect, such as discovering shared files or choosing which file owns a target or customization (use directory-build-organization); item operations; target structure; broad reviews without a concrete property defect; non-MSBuild work."
license: MIT
---
# MSBuild Property Patterns
Canonical property definition and manipulation patterns from the MSBuild repository.
## 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>
```
### 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.
## 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.
## 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>
```
## 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>
```
### Preferred path functions
| Function | Purpose |
|---|---|
| `$([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 |
## Target Framework Detection Helpers
```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>
```
## 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'" />
```
## Feature Gating by MSBuild Version
```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>
```
## 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.
## 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.
© dotnet · MIT · dotnet/skills