11 мая 2013 г.

Continuous Integration: Создание собственной конфигурации

Скачать исходный код

Конфигурации сборки (build configurations) обеспечивают хранение нескольких версий настроек для солюшена и проектов. Создание новой конфигурации будет необходимым шагом для работы с несколькими окружениями и версиями релиза.

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

Что такое конфигурация Debug и Release

При создании проекта в Visual Studio по-умолчанию создается две конфигурации: Debug и Release. Многие знают, что при заливке на боевой сервер лучше собирать в Release, а для локальной работы подойдет Debug. Предлагаю детально посмотреть, что такое конфигурации и как они влияют на артефакты проекта.

Конфигурация сборки в проекте

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

MvcApplication.csproj:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>

Каждая группа свойств выставляется в зависимости от конфигурациии (Debug, Release,...) и платформы (AnyCPU, x64,...):
Condition=" '$(Configuration)|$(Platform)' == ....

Свойства DebugSymbols, Optimize и другие в дальнейшем буду параметрами компиляции проекта. Задача по сборке проекта вызывается в файле Microsoft.CSharp.targets:

<Csc  Condition=" '%(_CoreCompileResourceInputs.WithCulture)' != 'true' "
   AdditionalLibPaths="$(AdditionalLibPaths)"
   AddModules="@(AddModules)"
   AllowUnsafeBlocks="$(AllowUnsafeBlocks)"
   ApplicationConfiguration="$(AppConfigForCompiler)"
   BaseAddress="$(BaseAddress)"
   CheckForOverflowUnderflow="$(CheckForOverflowUnderflow)"
   CodePage="$(CodePage)"
   DebugType="$(DebugType)"
   DefineConstants="$(DefineConstants)"
   DelaySign="$(DelaySign)"
   DisabledWarnings="$(NoWarn)"
   DocumentationFile="@(DocFileItem)"
   EmitDebugInformation="$(DebugSymbols)"
   EnvironmentVariables="$(CscEnvironment)"              
   ErrorEndLocation="$(ErrorEndLocation)"
   ErrorReport="$(ErrorReport)"
   FileAlignment="$(FileAlignment)"
   GenerateFullPaths="$(GenerateFullPaths)"
   HighEntropyVA="$(HighEntropyVA)"
   KeyContainer="$(KeyContainerName)"
   KeyFile="$(KeyOriginatorFile)"
   LangVersion="$(LangVersion)"
   LinkResources="@(LinkResource)"
   MainEntryPoint="$(StartupObject)"
   ModuleAssemblyName="$(ModuleAssemblyName)"
   NoConfig="true"
   NoLogo="$(NoLogo)"
   NoStandardLib="$(NoCompilerStandardLib)"
   NoWin32Manifest="$(NoWin32Manifest)"
   Optimize="$(Optimize)"
   OutputAssembly="@(IntermediateAssembly)"
   PdbFile="$(PdbFile)" 
   Platform="$(PlatformTarget)"
   Prefer32Bit="$(Prefer32Bit)"
   PreferredUILang="$(PreferredUILang)"
   References="@(ReferencePath)"
   Resources="@(_CoreCompileResourceInputs);@(CompiledLicenseFile)"
   ResponseFiles="$(CompilerResponseFile)"
   Sources="@(Compile)"
   SubsystemVersion="$(SubsystemVersion)"
   TargetType="$(OutputType)"
   ToolExe="$(CscToolExe)"
   ToolPath="$(CscToolPath)"
   TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
   UseHostCompilerIfAvailable="$(UseHostCompilerIfAvailable)"
   Utf8Output="$(Utf8Output)"
   WarningLevel="$(WarningLevel)"
   WarningsAsErrors="$(WarningsAsErrors)"
   WarningsNotAsErrors="$(WarningsNotAsErrors)"
   Win32Icon="$(ApplicationIcon)"
   Win32Manifest="$(Win32Manifest)"
   Win32Resource="$(Win32Resource)"
   />           

Задача Csc является стандартной и фактически вызывает компилятор csc.exe. Свойства задачи будут переданы в командную строку компилятора.

Мы увидели, что конфигурация сборки (build configuration) определяет набор параметров для компиляции проекта (возможно еще какие-то свойства). Например, в конфигурации Debug свойство Optimize выставлено в false, а в Release оно выставлено в true. Это, в итоге, приводит к тому, что компилятору передается Optimize="$(Optimize)" и мы получаем оптимизированный код в Release и неоптимизированный в Debug.

Управление конфигурацией проекта через интерфейс

Настройки, которые мы видим в файле проекта, отражены во вкладке Project Properties. Для примера я поменял несколько настроек для конфигурации Debug:

Секция свойств Debug в файле проекта отражает изменения, которые мы сделали в интерфейсе, через выставление соответствующих свойств:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\</OutputPath>
    <DefineConstants>DEBUG</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>3</WarningLevel>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>

Для более точной настройки конфигурации рекомендую делать изменения внучную прямо в XML-код файла проекта, т.к. интерфейс имеет очень ограниченный набор свойств. Вам может понадобится определить/переопределить свойства для сборки, добавить файлы для компиляции/настройки проекта и т.п.

Конфигурация сборки в солюшене

На уровне солюшена хранится список проектов, активные конфигураци и информация обо всех конфигурациях по каждому проекту.

MvcApplication.sln:

...
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcApplication", "MvcApplication\MvcApplication.csproj", "{BB24DDE3-EB20-47A9-8A70-CFCCAA1613AD}"
EndProject
...
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcApplicationNumberTwo", "MvcApplicationNumberTwo\MvcApplicationNumberTwo.csproj", "{9622301F-9699-482D-9394-DF0B05B5CAB4}"
EndProject
...
Global
 GlobalSection(SolutionConfigurationPlatforms) = preSolution
  Debug|Any CPU = Debug|Any CPU
  Release|Any CPU = Release|Any CPU
 EndGlobalSection
 GlobalSection(ProjectConfigurationPlatforms) = postSolution
  {BB24DDE3-EB20-47A9-8A70-CFCCAA1613AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  {BB24DDE3-EB20-47A9-8A70-CFCCAA1613AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
  {BB24DDE3-EB20-47A9-8A70-CFCCAA1613AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
  {BB24DDE3-EB20-47A9-8A70-CFCCAA1613AD}.Release|Any CPU.Build.0 = Release|Any CPU
  {9622301F-9699-482D-9394-DF0B05B5CAB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  {9622301F-9699-482D-9394-DF0B05B5CAB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
  {9622301F-9699-482D-9394-DF0B05B5CAB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
  {9622301F-9699-482D-9394-DF0B05B5CAB4}.Release|Any CPU.Build.0 = Release|Any CPU
 EndGlobalSection
        ...

Настройку этой секции можно делать прямо в файле солюшена, либо через интерфейс студии:

Более подробно о секциях файла солюшена и его взаимодействии с файлами проектов можно прочитать в MSDN:

Создание новой конфигурации

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

Создать новую конфигурацию можно вручную, добавив нужный код в файл .sln и в файлы проектов, где это необходимо. Для простоты мы рассмотрим, как это делается через интерфейс Visual Studio. Будем создавать конфигурацию для UAT (название может быть произвольным, но должно быть уникальным):

В окне New Solution Configuration стоит обратить внимание на два поля:

— Create new project configurations

Если опция выбрана, то после нажатия Ок в каждом проекте, который есть в солюшене, будет создана секция типа:

  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'UAT|AnyCPU'">
    
  <PropertyGroup>

— Copy Settings from

Если из возможных вариантов мы выбираем <Empty>, то в проектах добавляется только:

  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'UAT|AnyCPU'">
    <OutputPath>bin\</OutputPath>
  <PropertyGroup>

Если из возможных вариантов мы выбираем существующую конфигурацию, то в каждом проекте создается новая секция свойств, которая копирует все свойства из выбранной конфигурации именно этого проекта:

  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'UAT|AnyCPU'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\</OutputPath>
    <DefineConstants>DEBUG</DefineConstants>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    
  </PropertyGroup>

Создание config-файлов для новой конфигурации

В данный момент в проектах у нас есть файлы Web.config, Web.Debug.config и Web.Release.config. Мы создали новую конфигурацию UAT и нам надо добавить файл Web.UAT.config, чтобы описать там трансформации для UAT-сервера.

Мы можем явно создать нужный файл и поправить XML-код проекта. Перед тем, как лезть в код и вручную вносить изменения разберемся, что мы должны изменить. Через интерфейс Visual Studio выбираем Web.config, к который хотим добавить трансформацию для UAT, щелкаем правой кнопкой и нажимаем Add Config Transform:

Visual Studio создала файл Web.UAT.config и добавила в XML-код проекта:

    <None Include="Web.UAT.config">
      <DependentUpon>Web.config</DependentUpon>
    </None>

После добавления новых файлов конфигурации мы должны увидеть:

Нам осталось написать нужные трансформации для UAT в файле Web.UAT.config, как мы делали в статье Continuous Integration: Трансформация Web.config. Во время сборки проекта, с выбранной конфигурацией UAT, трансформация будет автоматически применена к файлу Web.config за счет:

...
  <Target Name="AfterBuild">
    <TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="Web.config" StackTrace="true" />
  </Target>
...

Итоги

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


Статья из серии Continuous Integration: Работа с Config-файлами:

  1. Continuous Integration: Трансформация Web.config
  2. Continuous Integration: Процесс сборки проекта и трансформации Config-файлов
  3. Continuous Integration: Создание собственной конфигурации
  4. Continuous Integration: Трансформация App.config
  5. Continuous Integration: Рефакторинг файлов конфигурации

Комментариев нет:

Отправить комментарий