Feel Good.

28 марта 2012

Семантическое версионирование в DVCS Mercurial

Эта статья о том, как я наладил семантическое версионирование (semantic versioning) в своем проекте на базе DVCS Mercurial (можно читать как Git). На хабре есть отличный перевод про Семантическое управление версиями, советую начать с него.

В качестве хранилища кода, я выбрал mercurial. Помимо функции хранилища кода, mercurial будет выступать еще и как провайдер версий, это означает, что запрашивать текущую семантическую версию (Major.Minor.Patch) мы будем именно у него.

Итак, рассмотрим пару Major.Minor. Обычно эта пара всегда задается вручную в момент релиза, и самый простой способ это сделать - пометить руками нужный сhangeset тегом, содержащим версию релиза (например: 1.4, или v1.4, но не 1.4.3, так как path мы договоримся вычислять).

С Patch все намного сложнее. Наша цель, добиться чтобы при каждой фиксации(commit) изменений кодовой базы автоматически бы инкрементировался path-номер. Это примерно означало бы, что path будет равен числу фиксаций в текущем релизе (например: 1.4.17 - 17-ая фиксация в релизе 1.4, но в то же время 1.5.0 уже новый релиз, с новой path-нумерацией). Но здесь не стоит забывать один факт: мы ведь работаем с DVCS и у нас нет единого "брокера" path-номеров...

В интернете я нашел несколько вариантов решения этой задачи (SO, RSDN), где в основном предлагалось использовать число, полученное как {latesttagdistance}, но можно заметить, что {latesttagdistance} перестает работать в общем случае, когда тег может содержать произвольный текст. Но эта идея мне очень понравилась, и я начал искать универсальное решение. И вскоре решение было найдено.

Я предлагаю в качестве path-номера брать разницу между номером ревизии текущего changeset-а (замечу, что это не всегда tip) и номером ревизии changeset-а, имеющего тэг с релизной версией и являющегося ближайшим предком по отношению к текущему changeset-у.

На текущий момент, при таком подходе мне удалось достичь желаемого семантического версионирования своих проектов.

Теперь осталось все это как-то оформить. В конечном же счете, я преследовал цель автоматической генерации файла AssemblyInfo.cs на основе номера версии полученной от mercurial до начала сборки проекта, и с последующей сборкой. Очевидным решением здесь было сделать Custom MSBuild Task/Target, и в итоге появился проект Devme.MSBuildTasks.


Коротко о Devme.MSBuildTasks (текущая версия 0.3.0)

Q: Какие системы контроля версий поддерживает библиотека?

A: Пока только для mercurial.

Q: Как я могу начать использовать это в своем проекте?

A: Необходимо выполнить 3 простых шага:

Шаг 1. Используя Nuget, установить/скачать библиотеку к себе в проект.

Шаг 2. Создать в каталоге Properties (если использовали nuget, то файл шаблон будет создан автоматически) файл шаблон следующего содержания (за основу можно взять Ваш текущий файл AssemblyInfo.cs):

using System.Reflection;
 
[assembly: AssemblyTitle("todo")]
[assembly: AssemblyDescription("todo")]
[assembly: AssemblyCompany("todo")]
[assembly: AssemblyProduct("todo")]
[assembly: AssemblyCopyright("Copyright © todo")]
 
[assembly: AssemblyVersion("${major}.${minor}.${path}")]
[assembly: AssemblyFileVersion("${major}.${minor}.${path}")]
[assembly: AssemblyInformationalVersionAttribute("${major}.${minor}.${path}-${branch} ${hash|short}")]
Формат шаблона думаю Вам понятен, здесь в качестве ${some} будут подставлены соответствующие значения взятые из mercurial (см wiki). У себя я называю этот файл AssemblyInfo.cs.template.

Шаг 3. Открыть в блокноте файл проекта (*.csproj) и добавить в самом конце пару тэгов (или расширить существующую цель BeforeBuild), указав правильный путь к Devme.MSBuildTasks.dll:

<UsingTask 
    TaskName="SemanticVersioningTask" 
    AssemblyFile="ПУТЬ К ФАЙЛУ Devme.MSBuildTasks.dll" />
<Target Name="BeforeBuild">
    <semanticversioningtask 
        TemplateFilePath="Properties/AssemblyInfo.cs.template" 
        OutputFilePath="Properties/AssemblyInfo.cs" />
</Target>
Все готово!

Заключение
  1. Следует помнить, что каждый раз, во время сборки проекта, файл AssemblyInfo.cs будет генерироваться автоматически, поэтому его можно(надо) исключить из кодовой базы добавив в .hgignore.
  2. Так же не рекомендуется вносить в файл AssemblyInfo.cs любые изменения. Все изменения надо делать в шаблоне AssemblyInfo.cs.template.
  3. Исходники
  4. Wiki
Ссылки:
  1. Semantic Versioning
  2. AssemblyVersion и git. Давайте жить дружно.
  3. Полуавтоматическое выставление номера версии с помощью git

Всем приятного использования!

13 марта 2012

Stopwatch. Новые идеи.

Как-то давно я написал статью "Пишем обертку для Stopwatch", в которой рассказал как удобно было бы использовать стандартный класс Stopwatch из System.Diagnostics, сделав для него IDisposable обертку. И вот наконец, у меня появились новые идеи и свободное время и я решил вернуться к этой теме и немного ее доработать.

1. API. Я решил сделать единую точку входа - статический класс StopwatchManager со статическим методом Start. Все очень просто и понятно:

using (StopwatchManager.Start(elapsed => Console.WriteLine("Elapsed: {0}", elapsed)))
{
Thread.Sleep(2345);
}

2. Регионы (new!). Как часто Вам требовалось узнать не только текущее время выполнения блока, а например, еще и общее или суммарное время выполнения, либо узнать сколько раз блок выполнился, либо узнать минимальное, максимальное или среднее время выполнения? Теперь это стало проще благодаря именованным регионам:

using (StopwatchManager.Start("region", elapsed => 
Console.WriteLine("Third. " +
"Elapsed: {0}, " +
"Total: {1}, " +
"Count: {2}, " +
"Min: {3}, " +
"Max: {4}, " +
"Avg: {5}",
elapsed.Current,
elapsed.Total,
elapsed.Count,
elapsed.Min,
elapsed.Max,
elapsed.Avg
)))
{
Thread.Sleep(500);
}

Здесь “region” это идентификатор региона, задав его мы указываем, что хотим собирать статистику для помеченного блока кода. В статистику входят: Current – текущее, Avg - среднее, Min – минимальное, Max – максимальное, Total – общее время выполнения, Count – количество прогонов.


3. NuGet package (new!). Для удобства использования, я собрал все это в NuGet пакет, который можно скачать здесь, или через консоль:

PM> Install-Package Devme.Diagnostics


Исходники
Wiki

Всем приятного использования!