Изменение настроек проекта при помощи NuGet пакета на примере поддержания единого стиля кода в команде

NuGet logoРассмотрим следующую задачу: необходимо поддерживать общий стиль кода в разных проектах, даже если разработка и поддержка осуществляется разными командами.

Один из вариантов решения этой задачи – использовать StyleCop для анализа кода. Ему потребуется файл ".ruleset" с правилами, определяющими допустимые стили. Кроме того, необходимо настроить каждый проект, указав путь до такого файла.

В случае если проектов мало, то это может показаться простой задачей. Но как быть, если проектов много и располагаются они в разных репозиториях. Да еще и файл с правилами может изредка изменяться. Обновлять все проекты вручную, копируя правила в каждый из них и периодически проверять не изменены ли они в отдельно взятом проекте? Это уже ресурсоемкая задача.

Для упрощения этой задачи можно создать совй NuGet пакет. Он должен будет установить StyleCop.Analyzers в проект и задать определенный файл стилей. Для последнего потребуется изменить настройки проекта, добавив новое свойство.

Изменение настроек проекта при помощи NuGet пакета

Начиная с версии 2.5 NuGet получил возможность влиять на настройки проекта при помощи файлов двух типов:

  • (имя пакета).props – служит для установки значений для существующих и новых свойств проекта, импортируется в самом начале процесса;
  • (имя пакета).targets – предназначен для добавления дополнительных задач при сборке проекта и импортируется когда все свойства определены.

Стоит учесть, что MSBulid позволяет использовать локальные Directory.Build.props и Directory.Build.targets для каждого проекта. Они импортируются после файлов из SDK и NuGet пакетов, позволяя модифицировать уже заданные значение. Кроме того, сам проект может переопределять уже заданные свойства.

Необходимо отметить, что свойства и задачи из указанных файлов не внедряется непосредственно в файл проекта. Вместо этого, в случае с .NET Framework проектов, в него добавляются директивы <Import>, ссылающиеся на файлы из NuGet пакета и расположенные в месте, куда пакет был загружен. Для .NET Standard и .NET Core проектов в файл будет давлена только зависимость от пакет. Позже, во время сборки, будет создан временный ".props" файл, в котором будет также содержаться <Import>.

Как следствие, если несколько проектов используют общую папку для NuGet пактов, то все они будут использовать один файл ".ruleset".

По соглашениям NuGet, файлы ".props" и ".targets" должны быть расположены в папке build. В случае зависимости файлов от целевой платформы проекта, необходимо создать подпапки с именами соответствующих платформ (аналогично структуре папки libs). Например пакет MyProject.Contracts может иметь следующую структуру build:

/build
    /netstandard1.0
        MyProject.Contracts.props
        MyProject.Contracts.targets
    /net45
        MyProject.Contracts.props
        MyProject.Contracts.targets

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

/build
    MyProject.Contracts.props
    MyProject.Contracts.targets

NuGet пакет для контроля стилей кода

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

Подготавливаем правила стиля кода

Описание процесса создания файла правил и соответствующей настройки в файле проекта можно найти в статье "StyleCop для .NET Core и .NET Standard". Будем считать что правила уже созданы и сохранены в файле CompanyCodeStyles.ruleset.

Создаем структуру NuGet пакета

Файлы NuGet пакета будут расположены следующим образом:

/build
    Company.CodeStyleRules.props
/lib
    _._
/rules
    CompanyCodeStyles.ruleset
Company.CodeStyleRules.nuspec

Всего должно быть создано 3 папки:

  • build – предназначена для ".props" и ".target" файлов, хранит Company.CodeStyleRules.props со свойствами для добавления в проект.
  • lib – используется для библиотек, которые будут добавлены в проект. В создаваемом пакете содержит только файл нулевого размера со специальным именем "_._". В данном случае он обозначает отсутствие библтотек для добавления в проект.

Файл "_._" указывает на то, что папка нужна для структуры пакета, но не содержит файлов для добавления в проект. В создаваемом пакете он используется для папки lib, котрая, в данном случае, не содержит библиотек, но нужна для структуры пакета. Конечно, можно было обойтись без нее, но в интернет можно найти жалобы, что иногда NuGet работает некорректно если в пакете нет хотя бы одной из следующих папок: lib или content.

  • rules – нужна для файла с правилами стиля кода.

Имена первых двух папок диктуются соглашениями NuGet по созданию проекта и не могут быть изменены. Последняя папка имеет произвольное имя.

Спецификацию NuGet пакета Company.CodeStyleRules.nuspec можно создать при помощи команды:

NuGet.exe spec Company.CodeStyleRules

Кроме заполнения свойств, в него необходимо добавить:

  • Минимальную версию NuGet, которая поддерживает ".props" файлы –  2.5.
  • Список папок для добавления в пакет (build, libs, rules), т.к. не все их имена соответствуют соглашению по-умолчанию.
  • Зависимость от пакета StyleCop.Analyzers, который содержит анализаторы исходного кода.
  • Флаг <developmentDependency>true</developmentDependency>, который обозначает что данный пакет необходим только для разработки. NuGet не будет автоматически добавлять его как зависимость в другие пакеты, где он используется в процессе из создания.

В результате спецификация будет выглядеть следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<package>
  <metadata minClientVersion="2.5">
    <id>Company.CodeStyleRules</id>
    <version>1.0.0</version>
    <authors>Company</authors>
    <owners>Company</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <developmentDependency>true</developmentDependency>
    <description>Company code style rules.</description>
    <dependencies>
      <dependency id="StyleCop.Analyzers" version="1.1.118" />
    </dependencies>
  </metadata>
  <files>
    <file src="lib\**" target="lib" />
    <file src="build\**" target="build" /> 
    <file src="rules\**" target="rules"/>    
  </files>
</package>

Для корректной работы StyleCop.Analyzers необходимо в свойства проекта добавить путь до файла CompanyCodeStyles.ruleset. Для этого и потребуется Company.CodeStyleRules.props.

Создаем .props файл

Файл свойств должен содержать только значения необходимые для добавления в проект. При этом он должен иметь структуру, аналогичную xml файлу проекта. Например, .NET Standard проект с настройкой для StyleCop.Analyzers может выглядеть следующим образом:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    ...
    <CodeAnalysisRuleSet>..\CompanyCodeStyles.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  
  <ItemGroup>
    ...
  </ItemGroup>
</Project>

Соответственно, Company.CodeStyleRules.props будет включать в себя данную настройку и путь до неё:

<Project>
  <PropertyGroup>
    <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)..\rules\CompanyCodeStyles.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
</Project>

В .NET Framework прокетках свойство расположено аналогично, поэтому можно использовать один файл для всех платформ.

Стоит обратить внимание на путь до CompanyCodeStyles.ruleset. В самом проекте он, как правило, задан относительно файла проекта. Однако, в данном случае файл ".ruleset" находится по месту установки NuGet пакета, которое заранее не известно (даже занчение по умолчаняю для пути установки пакетов может быть переопределено).

Для решения данной проблемы можно воспользоваться свойством MSBuild под названием $(MSBuildThisFileDirectory). В ней содержится абсолютный путь до папки с текущим файлом. Его можно легко достроить до CompanyCodeStyles.ruleset, зная структуру пакета. Описание других свойств MSBuild можно найти в документации на сайте docs.microsoft.com

Для .NET Framework проектов можно сделать вариант NuGet пакета, который будет копировать файл ".ruleset" в папку проекта и использовать путь относительно нее. Однако для .NET Standard и .NET Core проектов такой подход не доступен, т.к. в них будет добавлена лишь ссылка на файл. А сам файл будет расположен в месте установки пакета. Текущее решение как раз подходит для такого случая. При этом оно также будет работать и для .NET Framework проектов.
Более того, в этом случае можно добиться того, что все проекты будут использовать один и тотже ".ruleset" файл. Для этого они должны иметь общую папку для установленных NuGet пакетов.

Остается только собрать пакет выполнив:

NuGet.exe pack Company.CodeStyleRules.nuspec

После этого необходимо загрузить новый пакет на локальный NuGet сервер для использования в проектах.

В завершении стоит отметить, что если даже локально, на компютере отдельного разработчика, правила из созданного пакета будут изменены, то это не затронет других разработчиков и build-сервер. А при восстановлении или установке новой версии пакета файл CompanyCodeStyles.ruleset будет заменен на оригинальный.

Добавить комментарий