воскресенье, 24 июня 2012 г.

Генерация Assembly Version при помощи T4 шаблонов

[Generation of Assembly Version using the T4 templates]

Assembly Version используется .NET фреймворком во время построения и во время исполнения приложения для определения загружаемой сборки. Другими словами, Assembly Version является важной частью имени сборки, ее однозначно определяющей.

Assembly File Version является номером версии файла (отображается в Windows Explorer) и НЕ ИСПОЛЬЗУЕТСЯ .NET-ран-таймом для локализации сборки.

Эти версии указываются в атрибутах сборки и как правило находятся в файле AssemblyInfo.cs:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


Если сборки создаются очень часто, ручное изменение значений указанных атрибутов становится накладным. Для автоматического увеличения номера построения (Build Number) и ревизии (Revision) можно использовать символ звездочки (*):
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.0.*")]


Компилятор сгенерирует номера самостоятельно, и этого в большинстве случаев будет достаточно. Но выдаваемые номера трудно поддаются интерпретации , и если хочется наделить числа большим смыслом, например, привязать к дате/времени, придется делать это при помощи сторонних средств. Для этих целей есть множество утилит и надстроек к Visual Studio в виде AddIn-ов, я же воспользуюсь встроенными средствами Visual Studio - генерацей кода по текстовым шаблонам: Text Template Transformation Toolkit, или коротко T4. При помощи этого инструмента можно генерировать заветные строки самостоятельно, наделив нумерацию необходимой логикой. У меня в примере нумерация привязана к году, месяцу, числу, и часу, когда создается сборка. Добавьте новый файл шаблона в проект, и назовите его, например, “AssemblyVersion.tt”:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
// Assembly Version
// Generated at <#= DateTime.Now #>
//
// [major version].[minor version].[build number].[revision number]
// build number = yy MM = Year Month (e.g.: 1102 = 2011 February)
// revision number = ddHH = Day Hour (e.g.: 1411 = 14 "February" at 11 )
//
// to generate for every build in BuildEvents:
// "%CommonProgramFiles%\Microsoft Shared\TextTemplating\10.0\TextTransform.exe" "$(ProjectDir)AssemblyVersion.tt" -out "$(ProjectDir)AssemblyVersion.cs"

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyVersion("1.0.<#= DateTime.Now.ToString("yyMM") #>.<#= DateTime.Now.ToString("ddHH") #>")]
[assembly: AssemblyFileVersion("1.0.<#= DateTime.Now.ToString("yyMM") #>.<#= DateTime.Now.ToString("ddHH") #>")]


Генерация кода по файлам шаблонов (*.tt) происходит автоматически при сохранении изменений в шаблоне или при явном вызове соответствующей команды (например из контекстного меню). Также можно попросить Visual Studio обработать все шалоны проекта. Но нам нужно добиться генерации при каждом построении (build) проекта. Для этого можно воспользоваться этим (http://msdn.microsoft.com/ru-ru/library/ee847423.aspx) руководством от MS "Создание кода в процессе построения".  Я же вызываю генератор шаблонов из командной строки в Prebuild-событии:

"%CommonProgramFiles%\Microsoft Shared\TextTemplating\10.0\TextTransform.exe" "$(ProjectDir)AssemblyVersion.tt" -out "$(ProjectDir)AssemblyVersion.cs"


Не забудьте удалить или закомментировать ненужные уже строки в файле AssemblyInfo.cs!


Замечания:
- Не уверен, но кажется, что генерация по шаблонам T4 не поддерживаются в Visual Studio Express - версии.
- Вам не обойтись от изменения версии файла (AssemblyFileVersion) если вы в проекте установки (Setup Project) используете параметр "RemovePreviousVersion=true". Файлы обновляются, только если имеют большую версию, чем уже установленые.
- Для проекта типа Class Library (Visual Basic) в Visual Studio 2010 автоматическая генерация версии при помощи знака (*) не срабатывает. Это известная проблема описана здесь (https://connect.microsoft.com/VisualStudio/feedback/details/588047), поэтому для автоматической генерации вам, наверное, придется использовать "внешние" средства, такие как T4.
- Если необходимо, чтобы все сборки проекта имели одинаковый номер версии, можно воспользоваться следующим способом:
    -- Удалите определяющие версию атрибуты во всех проектах (файлы AssemblyInfo).
    -- В одном из проектов создайте файл с атрибутами версии (AssemblyVersion, AssemblyFileVersion). Можно воспользоваться описанной в статье кодо-генерацией.
    -- Добавьте этот файл к каждому из проектов, но КАК ССЫЛКУ (as link).

------------------------------------------

Ресурсы:
How to use Assembly Version and Assembly File Version
http://support.microsoft.com/kb/556041/en-us

Создание кода и текстовые шаблоны T4
http://msdn.microsoft.com/ru-ru/library/bb126445

Official T4 team blog - официальный блог команды разработчиков t4
http://blogs.msdn.com/b/t4/
И их официальный блог примеров
http://t4talk.codeplex.com/

среда, 13 июня 2012 г.

Visual Studio Setup Project. Сохранение и восстановление установочного пути в реестре

[Visual Studio Setup Project. Reading and writing the installation path in the registry]

Setup Project в VisualStudio можно настроить так, чтобы перед установкой программы предыдущая версия удалялась: RemovePreviousVersion = true. На всякий случай напомню, что для корректной работы этого параметра необходимо увеличивать версию установочной программы - свойство Version.


Но есть одна неприятная вещь: путь, предлагаемый в диалоге выбора пути установки, полностью игнорирует выбор, сделанный пользователем в предыдущий раз. Установка удаляет старую версию программы и начинает новую практически с чистого листа. О сохранении каких-либо параметров нужно беспокоится самому. К счастью, есть возможность сделать это относительно просто, не написав, при этом, ни строчки кода. Решение проверено в немецкой Visual Studio 2010 (соответствующие “немецкие” скриншоты, не пугайтесь), уверен будет работать в VS 2008, и наверняка в VS 2005. Для этого нужно выполнить следующие шаги.

1. Установка пути по умолчанию. Во время первой установки необходимо предложить путь и здесь для вас ничего не меняется - определите DefaultLocation как вы это делали раньше (обычно это пусть к каталогу "Program Files" и далее в папку вашей фирмы и/или вашего продукта). Значение этого пути сохраняется во внутренней служебной переменой [TARGETDIR], пользователь может изменить его в диалоге выбора пути установки.


 2. Сохраняем путь в реестре. Для этого нужно определить создаваемую во время установки в реестре строку, например, как у меня под именем “HKEY_LOCAL_MACHINE\Software\[Manufacturer]\InstallPath”. Программа установки присвоит ей значение переменной “[TARGETDIR]”, которая будет содержать установочный путь.


3. Чтение пути из реестра. Для этого нужно определить "условие для запуска" (мой вольный перевод, лучше смотрите скриншот), которое читает реестр и сохраняет значение в определенной переменной. Установите в полях RegKey, Root и Value значения, которые вы задали для сохраняемого пути. И, наконец, трюк, который позволяет этому работать - в поле Property задайте имя переменной TARGETDIR.


PS. До этого решения мне помогла додуматься статья http://support.microsoft.com/kb/827026. Основное отличие от моего заключается в том, что в ней строковое значение реестра читается в пользовательскую переменную, которая присваивается полю “DefaultLocation” (свойство “пути приложения”). Но если ключа еще не существует (самая первая установка), путь остается не определен, и пользователь увидит, скорее всего, в диалоге выбора пути “C:\”.