Версии .NET сборки

Просто интересно, какой лучший подход к управлению версиями .NET сборки?

Я использую:

  • TFS 2013 для контроля версий
  • TFS gated check-ins
  • Wix 3.8 для кода пакета для файлов MSI

Я хочу установить версию:

  • сборки (AssemblyInfo.cs или общий, упомянутый во всех проектах)
  • Пакеты MSI (в коде Wix)
  • (например, внутри файла readme.txt в конечном результате сборки)
  • и т.д.

Идеальный номер версии позволит отслеживать установленное программное обеспечение обратно в точный исходный код.
Что-то вроде:

<Major>.<Minor>.<TFS_changeset_number>

Первые две части версии, которые я хочу хранить в некотором простом текстовом файле \XML в управлении версиями рядом с решением, поскольку я считаю, что они должны жить вместе. Разработчики будут обновлять этот файл вручную (например, после подхода Semantic Versioning). Каждая сборка будет читать этот файл версии, получить 3d часть версии от вызывающего инструмента CI и обновить все необходимые файлы с помощью версии.

Какой лучший способ реализовать это?

В прошлом я использовал несколько подходов:

1) Обертка NAnt\MsBuild, которая работает с этой версией, затем вызывает MsBuild для решения. Его можно вызвать из инструмента CI (Jenkins\TeamCity\etc).

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

2) настроить шаблон процесса сборки TFS

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

3) Отдельный проект MsBuild в решении, который выполняет только эту задачу управления версиями, и сконфигурирован для запуска сначала в Project Build Order решения VS.

Проблема - нужно ссылаться на этот метапроект во всех других проектах (включая все будущие), которые кажутся уродливыми

Я знаю разные пакеты расширения MsBuild и TFS, которые могут упростить обновления. Эта тема не о том, какая из них лучшая. Вопрос более методологический, чем технический.

Я также думаю, что было бы идеально, если бы Microsoft включила что-то для управления версиями в свой стандартный шаблон сборки TFS. Другие инструменты CI уже имеют эту функциональность (AssemblyInfo patcher).




ОБНОВЛЕНИЕ 11/09/2014

Я решил четко выразить Принципы управления версиями, которые будут соответствовать лучшим практикам Agile\Continuous Delivery:

1) Возможность воспроизведения любой исторической сборки

2) В результате 1) и в соответствии с принципами CD все (исходный код, тесты, конфиги приложений, env configs, build\package\deploy scripts и т.д.) хранятся под контролем версий и поэтому имеют версию, назначенную это

3) Номер версии хранится плотно вместе с исходным кодом, который он применяет для

4) Люди могут обновлять версию в соответствии с их бизнес-логикой маркетинга

5) Существует только 1 основная копия версии, которая используется во всех частях процесса автоматической сборки\упаковки

6). Вы можете легко сказать, какая версия программного обеспечения в настоящее время установлена ​​в целевой системе.

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

8) Очень просто сравнить версии, чтобы сказать, что ниже, а что выше - контролировать, какие сценарии upgrade\downgrade разрешены, и особенности их реализации




ОБНОВЛЕНИЕ 15/09/2014

Посмотрите мой собственный ответ ниже.
Мне посчастливилось найти решение, отвечающее всем моим требованиям!

Ответ 1

Я придумал решение, отвечающее всем моим требованиям, и удивительно простое!

IDEA

Поместите все пользовательские операции с версиями в пользовательский Version.proj MsBuild script и вызовите его в определении сборки TFS до .sln. script вводит версию в исходный код (SharedAssemblyInfo.cs, код Wix, readme.txt), а затем сборка решений создает исходный код.

Версия формируется из основных и второстепенных чисел, находящихся в файле Version.xml, хранящихся в TFS вместе с исходными кодами; и из номера набора изменений, представленного как TF_BUILD_SOURCEGETVERSION env var родительским процессом сборки TFS

enter image description here

Спасибо Microsoft за это:

  • TFS 2013 - передает TF_BUILD переменные окружения в процесс сборки, вот как я получаю номер набора изменений текущего создаваемого кода
  • MsBuild позволяет встроенные задачи в С# - для замены версии в исходных файлах с использованием класса Regex С#

Таким образом, нет необходимости использовать MsBuild или TFS community\extension packs\addons\whatever. И нет необходимости изменять стандартный шаблон процесса сборки TFS. Простое решение обеспечивает высокую ремонтопригодность!

РЕАЛИЗАЦИЯ

Version.proj

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!--
Run this script for every build in CI tool before building the main solution
If you build in TFS, simply add script as the first item in a list of projects under Process tab > Build > Projects
-->

  <PropertyGroup>
    <VersionFile>..\Version.xml</VersionFile>
    <MainProjectDir>... set this to main solution directory ...</MainProjectDir>
  </PropertyGroup>

  <Import Project="$(VersionFile)"/>
  <Import Project="Common.proj"/>

  <Target Name="GetMajorMinorNumbers">
    <Error Text="ERROR: MajorVersion is not set in $(VersionFile)" Condition="'$(MajorVersion)' == ''" />
    <Message Text="MajorVersion: $(MajorVersion)" />

    <Error Text="ERROR: MinorVersion is not set in $(VersionFile)" Condition="'$(MinorVersion)' == ''" />
    <Message Text="MinorVersion: $(MinorVersion)" />
  </Target>


  <Target Name="GetChangesetNumber">
    <Error Text="ERROR: env var TF_BUILD_SOURCEGETVERSION is not set, see http://msdn.microsoft.com/en-us/library/hh850448.aspx" Condition="'$(TF_BUILD_SOURCEGETVERSION)' == ''" />
    <Message Text="TF_BUILD_SOURCEGETVERSION: $(TF_BUILD_SOURCEGETVERSION)" />
  </Target>


  <Target Name="FormFullVersion">
    <PropertyGroup>
        <FullVersion>$(MajorVersion).$(MinorVersion).$(TF_BUILD_SOURCEGETVERSION.Substring(1))</FullVersion>
    </PropertyGroup>
    <Message Text="FullVersion: $(FullVersion)" />
  </Target>


  <Target Name="UpdateVersionInFilesByRegex">
    <ItemGroup>
        <!-- could have simplified regex as Assembly(File)?Version to include both items, but this can update only one of them if another is not found and operation will still finish successfully which is bad -->
        <FilesToUpdate Include="$(MainProjectDir)\**\AssemblyInfo.cs">
            <Regex>(?&lt;=\[assembly:\s*Assembly?Version\(["'])(\d+\.){2,3}\d+(?=["']\)\])</Regex>
            <Replacement>$(FullVersion)</Replacement>
        </FilesToUpdate>
        <FilesToUpdate Include="$(MainProjectDir)\**\AssemblyInfo.cs">
            <Regex>(?&lt;=\[assembly:\s*AssemblyFileVersion\(["'])(\d+\.){2,3}\d+(?=["']\)\])</Regex>
            <Replacement>$(FullVersion)</Replacement>
        </FilesToUpdate>
        <FilesToUpdate Include="CommonProperties.wxi">
            <Regex>(?&lt;=&lt;\?define\s+ProductVersion\s*=\s*['"])(\d+\.){2,3}\d+(?=["']\s*\?&gt;)</Regex>
            <Replacement>$(FullVersion)</Replacement>
        </FilesToUpdate>
    </ItemGroup>

    <Exec Command="attrib -r %(FilesToUpdate.Identity)" />
    <Message Text="Updating version in %(FilesToUpdate.Identity)" />
    <RegexReplace Path="%(FilesToUpdate.Identity)" Regex="%(Regex)" Replacement="%(Replacement)"/>
  </Target>



  <Target Name="WriteReadmeFile">
    <Error Text="ERROR: env var TF_BUILD_BINARIESDIRECTORY is not set, see http://msdn.microsoft.com/en-us/library/hh850448.aspx" Condition="'$(TF_BUILD_BINARIESDIRECTORY)' == ''" />
    <WriteLinesToFile
        File="$(TF_BUILD_BINARIESDIRECTORY)\readme.txt"
        Lines="This is version $(FullVersion)"
        Overwrite="true"
        Encoding="Unicode"/>
  </Target>

  <Target Name="Build">
    <CallTarget Targets="GetMajorMinorNumbers" />
    <CallTarget Targets="GetChangesetNumber" />
    <CallTarget Targets="FormFullVersion" />
    <CallTarget Targets="UpdateVersionInFilesByRegex" />
    <CallTarget Targets="WriteReadmeFile" />
  </Target>

</Project>

Common.proj

<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="12.0">
<!-- based on example from http://msdn.microsoft.com/en-us/library/dd722601.aspx -->
  <UsingTask TaskName="RegexReplace" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
    <ParameterGroup>
      <Path ParameterType="System.String" Required="true" />
      <Regex ParameterType="System.String" Required="true" />
      <Replacement ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Core" />
      <Using Namespace="System" />
      <Using Namespace="System.IO" />
      <Using Namespace="System.Text.RegularExpressions" />
      <Code Type="Fragment" Language="cs"><![CDATA[
            string content = File.ReadAllText(Path);
            if (! System.Text.RegularExpressions.Regex.IsMatch(content, Regex)) {
                Log.LogError("ERROR: file does not match pattern");
            }
            content = System.Text.RegularExpressions.Regex.Replace(content, Regex, Replacement);
            File.WriteAllText(Path, content);
            return !Log.HasLoggedErrors;
]]></Code>
    </Task>
  </UsingTask>

  <Target Name='Demo' >
    <RegexReplace Path="C:\Project\Target.config" Regex="$MyRegex$" Replacement="MyValue"/>
  </Target>
</Project>

Version.xml

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <MajorVersion>1</MajorVersion>
    <MinorVersion>1</MinorVersion>
  </PropertyGroup>
</Project>

Ответ 2

Хорошее горе, все эти сложные ответы.

В TFS 2013 это просто. Сайт сообщества TFS Build Extensions предлагает простой PowerShell, чтобы вытащить номер сборки из имени сборки, назначенного TFS.

enter image description here

Вы настраиваете формат номера сборки как "$ (BuildDefinitionName) _6.0.0 $(Rev:.r)", что приведет к чему-то вроде "6.0.0.1", где "1" увеличивается для каждой сборки.

Затем вы добавляете версию PowerShell версии script в свою сборку и автоматически сбрасываете номер версии выше и применяете ее ко всем файлам AssemblyInfo. * в папке сборки. Вы можете добавить дополнительные типы файлов, обновив script.

Ответ 3

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

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

Мы используем следующую настройку:

  • Установите формат сборки в определении сборки как что-то вроде: 1.0.0. $(BuildId)

  • В шаблоне процесса сборки в задаче MSBuild введите следующий элемент в элемент MSBuildArguments

    String.format("/p:BuildNumber={0}", BuildDetail.BuildNumber)
    ... убедитесь, что вы оставили то, что уже было там.
  • В ваших проектах (или, в идеале, общем файле реквизита, включенном во все ваши проекты) определено свойство, называемое номером сборки, которое по умолчанию имеет значение 0.0.0.1.

    <PropertyGroup><BuildNumber Condition="'$(BuildNumber)'==''">0.0.0.1</BuildNumber></PropertyGroup>
    обратите внимание, что вы можете еще больше разбить это вам нравится использовать функции свойств. Мы используем это, чтобы получить, например, число Major:
    <MajorVersionNumber>$(BuildNumber.Split('.')[0])</MajorVersionNumber>
    and yes, this does put a dependency on the build number format in our builds!
  • В вашем проекте вы можете использовать свойство номера сборки для ввода в различные файлы во время сборки. Вы можете использовать пользовательские задачи сборки (я использую "sed" и этот макрос для ввода номера версии в файл .h, например, то же самое можно сделать с любым типом файла на основе текста).

  • Если у вас более сложные требования к версии, вы можете использовать пользовательские цели MSBuild, которые вводят номер сборки в другие типы файлов. Я сделал именно это с версиями для пакетов NuGet, которые наши сборки автоматически создают для наших проектов CS общей библиотеки, например.

Чтобы запросить сборку по ее номеру сборки, вы можете сделать следующее в PowerShell (с установленными инструментами Visual Studio Team Foundation Server Power Tools):

Add-PSSnapin Microsoft.TeamFoundation.PowerShell # you must install the VS TFS Power tools with the powershell option enabled to get this... a must have IMHO
$tfs = Get-TfsServer http://yourtfsserver:8080/tfs/YourProjectCollectionName
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Build.Client')
$buildserver = $tfs.GetService([Microsoft.TeamFoundation.Build.Client.IBuildServer])
$buildQuerySpec = $buildserver.CreateBuildDetailSpec("YourTFSProjectName","Your-Build-Definition-Name")
$buildQuerySpec.BuildNumber = '1.0.0.12345' # put your build number here.
$buildQuerySpec.QueryDeletedOption = 'IncludeDeleted'
$bld = $buildserver.QueryBuilds($buildQuerySpec)

С помощью '$ bld' вы можете запросить все свойства этой конкретной сборки. Например, чтобы узнать, на каких изменениях была построена сборка, статус сборки, кто спровоцировал сборку, и если для сборки была полка:

$bld.Builds[0] | Ft -Property BuildFinished,RequestedFor,ShelvesetName,Status,SourceGetVersion -AutoSize

Изменить: Исправить опечатку в Powershell script

Ответ 4

Я загрузил этот script (спасибо ответ MrHinsh), зарегистрировался в script в исходном элементе управления и указал его в начале сборки сборки script:

BuildDefPreBuild

Затем я настроил формат номера сборки как "$ (BuildDefinitionName) _1.0.0 $(Rev:.r)" (подробнее см. MrHinsh).

И это работает к моему удивлению.

Ответ 5

Я работаю над проектом, который имеет похожие, но не идентичные требования. Th основных и второстепенных версий хранятся в AssemblyInfo, как и любой стандартный проект .net. На нашем сервере сборки у нас есть оболочка MsBuild script, которая вызывает сборку .sln, но также выполняет некоторые задачи настройки, включая создание дополнительной информации о сборке. Этот файл сборки выполняется только на нашем сервере сборки. Разработчики, создающие Visual Studio, будут только создавать .sln, а не получать дополнительное поведение.

Ответ 6

У нас есть аналогичное требование и используйте NANT ASMINFO TASK. Во время сборки TFS мы вызываем эту дополнительную цель NANT, которая создает новый файл AssemblyVersion.cs.

<asminfo output="AssemblyInfo.cs" language="CSharp">
<imports>
    <import namespace="System" />
    <import namespace="System.Reflection" />
    <import namespace="System.EnterpriseServices" />
    <import namespace="System.Runtime.InteropServices" />
</imports>
<attributes>
    <attribute type="ComVisibleAttribute" value="false" />
    <attribute type="CLSCompliantAttribute" value="true" />
    <attribute type="AssemblyVersionAttribute" value="${version.number}" />
    <attribute type="AssemblyTitleAttribute" value="My fun assembly" />
    <attribute type="AssemblyDescriptionAttribute" value="More fun than a barrel of monkeys" />
    <attribute type="AssemblyCopyrightAttribute" value="Copyright (c) 2002, Monkeyboy, Inc." />
    <attribute type="ApplicationNameAttribute" value="FunAssembly" />
</attributes>
<references>
    <include name="System.EnterpriseServices.dll" />
</references>

Обратите внимание на свойство ${version.number}, которое фактически задано на основе вашего требования. Затем мы просматриваем существующие файлы Assemblyversion.cs и делаем их только для чтения, а затем заменяем их новым созданным нами файлом.

<attrib readonly="false" file="${project}\AssemblyVersion.cs"/>

Как вы знаете, эта цель выполняется перед компиляцией.

Ответ 7

Я использую [Major]. [Minor]. [BuildNumber]. [revision]

Затем я могу вернуться к сборке, которая даст набор изменений, который даст рабочий элемент и т.д.

Вы можете использовать задачи сборки сообщества, или я откатываю свой собственный.

Я делаю то же самое для MSI и DacPac

в основном ссылаются на файл assemblyinfo, а затем обновляют номер с помощью регулярного выражения, в ежедневной сборке оставляют сетевую версию с одинаковым значением и просто обновляют версию файла, поэтому вы можете поддерживать совместимость

тот же метод для MSI и Dacapac только в разных местах. в MSI у меня есть Buildparams.wxi, который имеет следующую структуру

<?xml version="1.0" encoding="utf-8"?>
<Include>
    <?define ProductVersion="1.2.3.4"?>  
</Include>

Productversion затем используется как var.Productversion в скриптах wix. pre build я обновляю 1.2.3.4 с номером сборки, который я хочу использовать

Ответ 8

 <Target Name="GetTFSVersion" >  
    <Exec Command="$(TF) history /server:[tfs]\DefaultCollection&quot;$/FP4WebServices/Staging&quot; /stopafter:1 /recursive /login:domain\user,password /noprompt | find /V &quot;Changeset&quot; | find /V > $(LatestChangeSetTxtFile).tmp"/>
    <Exec Command="FOR /F &quot;eol=; tokens=1 delims=, &quot; $(PERCENT_SIGN)$(PERCENT_SIGN)i in ($(LatestChangeSetTxtFile).tmp) do $(AT_SIGN)echo $(PERCENT_SIGN)$(PERCENT_SIGN)i > $(LatestChangeSetTxtFile)"/>
    <ReadLinesFromFile File="$(LatestChangeSetTxtFile)">
      <Output TaskParameter="lines" PropertyName="ChangeSet"/>
    </ReadLinesFromFile>
    <Message Text="TFS ChangeSet: $(ChangeSet)"/>        
  </Target>

  <Target Name="SetVersionInfo" DependsOnTargets="GetTFSVersion">
    <Attrib Files="@(AssemblyInfoFiles)" Normal="true"/>
     <FileUpdate Files="@(AssemblyInfoFiles)" Regex="AssemblyFileVersion\(&quot;.*&quot;\)\]" ReplacementText="AssemblyFileVersion(&quot;$(Major).$(Minor).$(Build).$(ChangeSet)&quot;)]" />
   </Target>