Другое

Руководство по условному включению файлов в MSBuild

Узнайте, как настроить MSBuild в .csproj для условного включения файлов в зависимости от существования файлов. Руководство примерами.

Как настроить MSBuild в .csproj, чтобы условно включать файлы в зависимости от существования других файлов с определёнными сегментами пути?

Я пытаюсь изменить файл .csproj так, чтобы исходный файл включался только в том случае, если не существует другой файл с определённым именем. Например, я хочу включить mySourceFile.cs только если mySourceFile.netstandard.cs отсутствует.

Я пробовал следующую конфигурацию MSBuild:

xml
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
    <Compile Remove="*.cs" Condition="!Exists(%(Filename).Replace('.cs', '.netstandardonly.cs')" />
</ItemGroup>

Однако я получаю ошибку, указывающую, что ссылка на встроенную метадату Filename в этом условии недопустима. Как правильно реализовать условное включение файлов на основе существования других файлов с конкретными сегментами пути в MSBuild?

На основе проведённого исследования вы сталкиваетесь с распространённой ограниченностью MSBuild, когда встроенные метаданные, такие как %(Filename), нельзя напрямую использовать в условиях. Правильный подход заключается в использовании целей MSBuild и косвенной оценки свойств, чтобы добиться условного включения файлов на основе существования других файлов.

Ниже приведены эффективные решения для условного включения файлов в зависимости от их существования:

Содержание

Использование целей MSBuild для условного включения

Самый надёжный метод — использовать цели MSBuild для динамического изменения групп элементов на основе существования файлов. Согласно документации Microsoft Learn, MSBuild обрабатывает файлы проекта в несколько проходов, а оценка элементов происходит после оценки свойств.

xml
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="ConditionalFileInclusion" BeforeTargets="CoreCompile">
        <ItemGroup>
            <!-- Сначала определяем файлы, которые нуждаются в условном включении -->
            <ConditionalFiles Include="**/*.cs" Exclude="**/*.netstandard.cs" />
            
            <!-- Затем проверяем наличие соответствующих .netstandard.cs файлов и удаляем их, если они существуют -->
            <Compile Remove="@(ConditionalFiles)" Condition="Exists('%(Filename).netstandard.cs')" />
        </ItemGroup>
    </Target>
</Project>

Этот подход работает, потому что:

  • Цель запускается до CoreCompile, обеспечивая правильное время выполнения
  • Метаданные элементов, такие как %(Filename), доступны внутри групп элементов
  • Функция Exists() корректно оценивает пути файлов

Подход на основе свойств с проверкой существования файлов

Другой способ — использовать свойства для хранения результатов проверки существования и затем ссылаться на эти свойства в условиях:

xml
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <!-- Создаём свойства для отслеживания файлов, у которых есть соответствующие .netstandard версии -->
        <HasNetstandardFiles Condition="Exists('$(MSBuildProjectDirectory)**\%(Filename).netstandard.cs')">true</HasNetstandardFiles>
        <HasNetstandardFiles Condition="'$(HasNetstandardFiles)' == ''">false</HasNetstandardFiles>
    </PropertyGroup>

    <ItemGroup Condition="'$(HasNetstandardFiles)' == 'false'">
        <Compile Include="**/*.cs" />
    </ItemGroup>
</Project>

Однако этот подход имеет ограничения при сложном сопоставлении файлов, поэтому метод на основе целей обычно более надёжный.

Трансформация метаданных с известными свойствами

Исходя из исследований по известным метаданным, можно использовать трансформации для создания путей к файлам, которые нужно проверить:

xml
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="ConditionalFileInclusion">
        <ItemGroup>
            <!-- Создаём временные элементы с соответствующими путями .netstandard файлов -->
            <NetstandardFiles Include="**/*.cs">
                <NetstandardPath>%(Filename).netstandard.cs</NetstandardPath>
            </NetstandardFiles>
            
            <!-- Удаляем исходные файлы, если соответствующие .netstandard файлы существуют -->
            <Compile Remove="@(NetstandardFiles)" Condition="Exists(%(NetstandardFiles.NetstandardPath))" />
        </ItemGroup>
    </Target>
</Project>

Это использует тот факт, что %(Filename) является известным метаданным, содержащим имя файла без расширения, и вы можете создать пользовательские метаданные для построения соответствующих путей файлов.

Продвинутое решение на основе целей

Для вашего конкретного сценария включения mySourceFile.cs только в том случае, если mySourceFile.netstandard.cs не существует, вот полное решение:

xml
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="ConditionalFileInclusion" BeforeTargets="CoreCompile">
        <ItemGroup>
            <!-- Создаём список всех .cs файлов, которые могут иметь .netstandard.cs аналоги -->
            <SourceFiles Include="**/*.cs" />
            
            <!-- Создаём пути к соответствующим .netstandard.cs файлам -->
            <NetstandardCounterparts Include="@(SourceFiles)">
                <NetstandardPath>%(Filename).netstandard.cs</NetstandardPath>
            </NetstandardCounterparts>
            
            <!-- Удаляем исходные файлы, у которых есть соответствующие .netstandard файлы -->
            <Compile Remove="@(SourceFiles)" Condition="Exists(%(NetstandardCounterparts.NetstandardPath))" />
            
            <!-- Опционально: добавляем .netstandard.cs файлы, если они существуют -->
            <Compile Include="**/*.netstandard.cs" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
        </ItemGroup>
    </Target>
</Project>

Это решение:

  • Использует BeforeTargets="CoreCompile" для правильного времени выполнения
  • Создаёт промежуточные элементы для построения путей файлов
  • Использует %(Filename) метаданные внутри групп элементов, где это разрешено
  • Обрабатывает как удаление, так и включение логики

Лучшие практики и соображения

  1. Время выполнения цели: всегда используйте BeforeTargets или AfterTargets, чтобы контролировать, когда выполняется ваша логика.
  2. Использование метаданных: известные метаданные, такие как %(Filename), доступны только в группах элементов и целях, а не в условиях свойств.
  3. Построение путей: используйте пользовательские метаданные для создания путей файлов, которые нужно проверить.
  4. Производительность: минимизируйте количество вызовов Exists() группировкой проверок файлов.
  5. Логика, специфичная для фреймворка: добавляйте условия на основе $(TargetFramework) при необходимости.

Согласно обсуждениям на Stack Overflow, этот подход является самым надёжным для сложных сценариев условного включения файлов.

Ключевой вывод — условия MSBuild имеют специфические правила области видимости для метаданных, и самый надёжный способ реализации условного включения файлов — это использование целей с правильной манипуляцией группами элементов, а не прямые условные подходы.

Источники

  1. MSBuild Conditions - Microsoft Learn
  2. MSBuild Items - Microsoft Learn
  3. Conditional file inclusion based on file existence - Stack Overflow
  4. How does Condition Exists actually evaluate? - Microsoft Learn
  5. Conditional inclusion of SQL files in build - MSDN Forums

Заключение

Чтобы добиться условного включения файлов на основе существования других файлов с конкретными сегментами пути в MSBuild:

  1. Используйте цели MSBuild вместо прямых условных подходов для сложных сценариев.
  2. Используйте известные метаданные как %(Filename) внутри групп элементов, где это разрешено.
  3. Создавайте промежуточные элементы для построения путей файлов, которые нужно проверить.
  4. Контролируйте время выполнения цели с помощью BeforeTargets или AfterTargets.
  5. Проверяйте решение с разными структурами файлов и конфигурациями сборки.

Подход на основе целей обеспечивает самый надёжный и гибкий способ реализации ваших требований к условному включению файлов, избегая проблем с областью видимости метаданных, которые возникают при прямом условном подходе.

Авторы
Проверено модерацией
Модерация