Автоматическое встраивание информации о версии Mercurial в проекты Visual Studio c # - PullRequest
54 голосов
/ 05 марта 2010

Исходная проблема

При создании наших проектов я хочу, чтобы mercurial id каждого репозитория был встроен в продукт (ы) этого репозитория (библиотека, приложение или тестовое приложение).

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

Я также считаю очень полезным иметь возможность видеть, было ли приложение скомпилировано с чистой (неизмененные) ревизии из репозитория.«Hg id» полезно добавляет «+» к идентификатору набора изменений, когда в репозитории есть незафиксированные изменения, так что это позволяет нам легко увидеть, работают ли люди с чистой или измененной версией кода.

Мой текущийРешение подробно описано ниже и удовлетворяет основным требованиям, но с ним возникает ряд проблем.

Текущее решение

В настоящее время к каждому решению Visual Studio я добавляюследующие команды «Предварительная сборка события командной строки»:

cd $(ProjectDir)
HgID

Я также добавляю файл HgID.bat в каталог проекта:

@echo off
type HgId.pre > HgId.cs

For /F "delims=" %%a in ('hg id') Do <nul >>HgID.cs set /p =            @"%%a"

echo ;                  >> HgId.cs
echo     }              >> HgId.cs
echo }                  >> HgId.cs

вместе с файлом HgId.pre,который определяется как:

namespace My.Namespace {
/// <summary> Auto generated Mercurial ID class. </summary>
internal class HgID {
    /// <summary> Mercurial version ID [+ is modified] [Named branch]</summary>
    public const string Version =

Когда я собираю свое приложение, событие предварительной сборки запускается во всех библиотеках, создавая новый файл HgId.cs (который не находится под контролем ревизии) и вызываябиблиотека для повторной компиляции с новой строкой 'hg id' в 'Version'.

Проблемы с текущим решением

Основная проблема заключается в том, что поскольку HgId.cs повторнокрпри каждой предварительной сборке, поэтому каждый раз, когда нам нужно что-либо скомпилировать, все проекты в текущем решении перекомпилируются.Поскольку мы хотим иметь возможность легко выполнять отладку в наших библиотеках, мы обычно сохраняем множество библиотек, на которые есть ссылки в нашем основном прикладном решении.Это может привести к тому, что время сборки будет значительно больше, чем хотелось бы.

В идеале я хотел бы, чтобы библиотеки компилировались только в том случае, если содержимое файла HgId.cs действительно изменилось, а не былосоздается с точно таким же содержимым.

Вторая проблема этого метода заключается в его зависимости от конкретного поведения оболочки Windows.Мне уже приходилось несколько раз изменять командный файл, поскольку оригинальный работал под XP, но не под Vista, следующая версия работала под Vista, но не под XP, и, наконец, мне удалось заставить его работать с обоими.Будет ли он работать с Windows 7, однако, все догадываются, и со временем я вижу, что с большей вероятностью подрядчики будут рассчитывать на то, что наши приложения смогут создавать свои приложения на своих Windows 7. Box

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

Мои актуальные вопросы

Как бы вы решили / какВы решаете проблему, которую я пытаюсь решить?

Какие варианты лучше, чем то, чем я сейчас занимаюсь?

Отклонено Решение этих проблем

До того, как яРеализовав текущее решение, я посмотрел расширение Mercurials Keyword, так как оно казалось очевидным решением.Однако чем больше я смотрел на это и читал мнения людей, тем больше приходил к выводу, что делать это было неправильно.

Я также помню проблемы, вызванные подстановкой ключевых слов в проектахв предыдущих компаниях (мысль о том, что мне снова понадобится Source Safe, вселяет в меня чувство страха * 8 ').

Кроме того, я не особо хочу включать расширения Mercurial для завершения сборки.Я хочу, чтобы решение было автономным, чтобы приложение не могло быть случайно скомпилировано без информации о встроенной версии только потому, что расширение не включено или не установлено правильное вспомогательное программное обеспечение.

Я также подумал о написании этого на лучшем языке сценариев, где я бы писал только файл HgId.cs , если содержимое действительно изменилось, но все варианты, которые я мог придумать, потребовали бы моегоколлеги, подрядчики и, возможно, клиенты должны установить программное обеспечение, которое им не нужно (например, cygwin).

Будут признательны за любые другие варианты, которые могут придумать люди.


Обновление

Частичное решение

Поработав с ним некоторое время, мне удалось получить файл HgId.bat, который перезаписывает файл HgId.cs только в случае его изменения:

@echo off

type HgId.pre > HgId.cst

For /F "delims=" %%a in ('hg id') Do <nul >>HgId.cst set /p =            @"%%a"

echo ;                  >> HgId.cst
echo     }              >> HgId.cst
echo }                  >> HgId.cst

fc HgId.cs HgId.cst >NUL
if %errorlevel%==0 goto :ok
copy HgId.cst HgId.cs
:ok
del HgId.cst

Проблемы с этим решением

Несмотря на то, что HgId.cs больше не создается каждый раз, Visual Studio sпока настаивает на компиляции каждый раз.Я пытался искать решения и проверял «Только строить запускаемые проекты и зависимости при запуске» в «Инструменты | Параметры | Проекты и решения» | «Построить и запустить», но это не имеет значения.

Вторая проблема также остается,и теперь у меня нет возможности проверить, будет ли он работать с Vista, поскольку этого подрядчика больше нет с нами.

  • Если кто-нибудь сможет протестировать этот пакетный файл на Windows 7 и / или Vista, я был бы признателен, если бы узнал, как он работает.

Наконец, моя эстетическая проблема с этимРешение, даже сильнее, чем было раньше, так как пакетный файл более сложный, и теперь есть еще что-то, что может пойти не так.

Если вы можете придумать какие-нибудь лучшие решения, я хотел бы услышать о них.

Ответы [ 7 ]

25 голосов
/ 23 июня 2010

Я только что выпустил небольшую задачу MSBuild с открытым исходным кодом, чтобы сделать именно то, что вам нужно:

  • Он помещает ваш номер версии Mercurial в вашу версию сборки .NET
  • Вы можете узнать из версии, была ли сборка с незафиксированными изменениями
  • Не вызывает ненужных сборок, если ревизия не изменилась
  • Не зависит от сценариев Windows
  • Ничего не устанавливается - вы просто добавляете небольшую DLL к своему решению и редактируете некоторые файлы в своем проекте

http://versioning.codeplex.com

16 голосов
/ 27 марта 2010

Я думаю, что у меня есть ответ для вас. Это будет немного сложным, но это избавит вас от необходимости делать какие-либо командные файлы. Вы можете положиться на MSBuild и Custom Tasks, чтобы сделать это для вас. Я использовал пакет расширений для MSBuild (доступен по адресу CodePlex ), но вторая задача, которая вам нужна, это то, что вы могли бы так же легко написать самостоятельно.

С этим решением вы можете щелкнуть правой кнопкой мыши на DLL и увидеть в свойствах файла, из какой версии Mercurial появилась DLL (или EXE).

Вот шаги:

  1. Получите пакет MBBuildExtension ИЛИ Запись пользовательских задач для перезаписи AssemblyInfo.cs
  2. Создать кастом Построить задачу в своем собственном проекте, чтобы получить Mercurial Id (код ниже).
  3. Редактировать файлы проекта, которые нуждаются в Mercurial Id для использования пользовательских задач (код ниже).

Настраиваемая задача для получения mercurial id: (Это нужно хорошо протестировать и, возможно, лучше обобщить ...)

using System;
using System.Diagnostics;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;


namespace BuildTasks
{
    public class GetMercurialVersionNumber : Task
    {
        public override bool Execute()
        {
            bool bSuccess = true;
            try
            {
                GetMercurialVersion();
                Log.LogMessage(MessageImportance.High, "Build's Mercurial Id is {0}", MercurialId);
            }
            catch (Exception ex)
            {
                Log.LogMessage(MessageImportance.High, "Could not retrieve or convert Mercurial Id. {0}\n{1}", ex.Message, ex.StackTrace);
                Log.LogErrorFromException(ex);
                bSuccess = false;
            }
            return bSuccess;
        }

        [Output]
        public string MercurialId { get; set; }

        [Required]
        public string DirectoryPath { get; set; }

        private void GetMercurialVersion()
        {
            Process p = new Process();
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.WorkingDirectory = DirectoryPath;
            p.StartInfo.FileName = "hg";
            p.StartInfo.Arguments = "id";
            p.Start();

            string output = p.StandardOutput.ReadToEnd().Trim();
            Log.LogMessage(MessageImportance.Normal, "Standard Output: " + output);

            string error = p.StandardError.ReadToEnd().Trim();
            Log.LogMessage(MessageImportance.Normal, "Standard Error: " + error);

            p.WaitForExit();

            Log.LogMessage(MessageImportance.Normal, "Retrieving Mercurial Version Number");
            Log.LogMessage(MessageImportance.Normal, output);

            Log.LogMessage(MessageImportance.Normal, "DirectoryPath is {0}", DirectoryPath);
            MercurialId = output;

        }
    }

И измененный файл проекта: (комментарии могут помочь)

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!--this is the import tag for the MSBuild Extension pack. See their documentation for installation instructions.-->
  <Import Project="C:\Program Files (x86)\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks" />
  <!--Below is the required UsingTask tag that brings in our custom task.-->
  <UsingTask TaskName="BuildTasks.GetMercurialVersionNumber" 
             AssemblyFile="C:\Users\mpld81\Documents\Visual Studio 2008\Projects\LambaCrashCourseProject\BuildTasks\bin\Debug\BuildTasks.dll" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProductVersion>9.0.30729</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{D4BA6C24-EA27-474A-8444-4869D33C22A9}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>LibraryUnderHg</RootNamespace>
    <AssemblyName>LibraryUnderHg</AssemblyName>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</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\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core">
      <RequiredTargetFramework>3.5</RequiredTargetFramework>
    </Reference>
    <Reference Include="System.Xml.Linq">
      <RequiredTargetFramework>3.5</RequiredTargetFramework>
    </Reference>
    <Reference Include="System.Data.DataSetExtensions">
      <RequiredTargetFramework>3.5</RequiredTargetFramework>
    </Reference>
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Class1.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

  <Target Name="Build" DependsOnTargets="BeforeBuild">
    <!--This Item group is a list of configuration files to affect with the change. In this case, just this project's.-->
    <ItemGroup>
      <AssemblyInfoFiles Include="$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs" />
    </ItemGroup>
    <!--Need the extension pack to do this. I've put the Mercurial Id in the Product Name Attribute on the Assembly.-->
    <MSBuild.ExtensionPack.Framework.AssemblyInfo AssemblyInfoFiles="@(AssemblyInfoFiles)"
                                                  AssemblyProduct="Hg: $(MercurialId)"
                                                  />
    <!--This is here as an example of messaging you can use to debug while you are setting yours up.-->
    <Message Text="In Default Target, File Path is: @(AssemblyInfoFiles)" Importance="normal" />
  </Target>  

  <Target Name="BeforeBuild">
    <!--This is the custom build task. The Required Property in the task is set using the property name (DirectoryPath)-->
    <BuildTasks.GetMercurialVersionNumber DirectoryPath="$(MSBuildProjectDirectory)">
      <!--This captures the output by reading the task's MercurialId Property and assigning it to a local
          MSBuild Property called MercurialId - this is reference in the Build Target above.-->
      <Output TaskParameter="MercurialId" PropertyName="MercurialId" />
    </BuildTasks.GetMercurialVersionNumber>
  </Target>
  <!--<Target Name="AfterBuild">
  </Target>-->

</Project>

Последнее примечание: проект задач сборки нужно построить только один раз. Не пытайтесь создавать его каждый раз, когда вы делаете остальную часть своего решения. Если вы это сделаете, вы обнаружите, что VS2008 имеет dll заблокирован. Еще не понял, что это, но я думаю, что лучше сделать это, построить dll, как вы хотите, а затем распространять ТОЛЬКО dll с вашим кодом, гарантируя, что местоположение dll фиксировано относительно каждого проекта, который вы должны использовать это в. Таким образом, никто не должен ничего устанавливать.

Удачи, и я надеюсь, что это поможет!

Оди

5 голосов
/ 25 марта 2010

Рассматривали ли вы использование строкового ресурса вместо строковой константы языка C #?Строковые ресурсы можно редактировать / заменять в выходных двоичных файлах после сборки, используя инструменты, предназначенные для локализации.

Вы бы отправили свой номер версии Mercurial в текстовый файл, который не используется сборкой C #, а затем с помощью операции после сборки замените ресурс версии фактическим значением из выпущенного текстового файла.Если вы подписываете свои сборки под строгим именем, замена подписи ресурса должна произойти до подписания.

Вот как мы решали эту проблему в Borland несколько лет назад для продуктов Windows.С тех пор мир стал более сложным, но этот принцип все еще действует.

2 голосов
/ 25 марта 2010

Мы решили эту проблему с другой системой контроля версий, Subversion.

Что мы делаем, так это то, что у нас есть файл commonassemblyinfo.cs, и мы подключаем к нему номер редакции svn, используя скрипт msbuild.

Мы буквально вызываем svninfo и очищаем вывод из ревизии: part, а затем вставляем его в файл CommonAssemblyInfo.cs.

Каждый проект в нашем решении имеет ссылку на общий файл и затем компилируется, что означает, что сборки компилируются с версией при компиляции с номером редакции svn, это также означает, что все зависимые библиотеки, которые мы написали, также имеют версии.

Мы достигли этого довольно легко с помощью файлов CruiseControl .net и msbuild.

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

У меня будет стандартный xml-файл, который читается при запуске приложения, и вы можете вставить информацию в него (работает и для файлов resx). Затем вы можете прочитать это значение обратно в коде. Немного неприглядно, но это работает.

Мое любимое решение - способ, которым мы это делаем, но вы не можете сделать это в Mercurial, поскольку информация о наборе изменений не является чисто числовой, как номер редакции svn.

2 голосов
/ 06 марта 2010

Я мой .hgrc У меня есть это:

[extensions]                                                                                                                                                               
hgext.keyword =                                                                                                                                                            
hgext.hgk =                                                                                                                                                                

[keyword]                                                                                                                                                                  
** =                                                                                                                                                                       

[keywordmaps]                                                                                                                                                              
Id = {file|basename},v {node|short} {date|utcdate} {author|user} 

И в моем исходном файле (Java) я делаю:

public static final String _rev = "$Id$";

После коммита $ Id $ превращается в нечто вроде:

public static final String _rev = "$Id: Communication.java,v 96b741d87d07 2010/01/25 10:25:30 mig $";
1 голос
/ 27 марта 2010

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

Вместо этого мы встраиваем номер редакции контроля исходного кода только в том случае, если наши проекты собраны в TeamCity на отдельном сервере сборки. Редакция встроена в AssemblyVersion и AssemblyFileVersion, разделенных на две последние части.

По умолчанию версия заканчивается в 9999,9999, и мы разделяем ревизию таким образом, чтобы ревизия 12345678 стала 1234,5678 (не то, что мы приближаемся к 12-миллионной ревизии ...).

Этот процесс гарантирует, что продукт, версия которого 2.3.1.1256, была определенно первоначальная сборка ревизии 11,256. Все, что разработчики собирают вручную, будет выглядеть следующим образом: 2.3.9999.9999.

Точные подробности того, как достигается вышеупомянутое, не имеют прямого отношения, поскольку мы не используем Mercurial, но кратко: TeamCity обрабатывает проверку требуемой ревизии и передает ее номер в наш скрипт MSBuild, который затем выполняет перезапись AssemblyInfo. .cs с помощью пользовательского задания мы написали.

Что мне особенно нравится в этом, так это то, что абсолютно никакие файлы не изменяются при нажатии F5 в Visual Studio - фактически, что касается Visual Studio, он работает с простым старым нормальным решением.

1 голос
/ 25 марта 2010

Кажется, есть несколько возможных подходов к этой проблеме. Первое и, вероятно, предпочтительное решение - установить сервер сборки и распространять только сборки, сгенерированные там, среди клиентов. Преимущество в том, что вы никогда не отправляете незафиксированные изменения. Используя MSBuild, NAnt или какой-либо другой инструмент сборки на основе задач, весь процесс становится очень гибким. Я смог установить TeamCity и получить первые пару сборок и запустить их без особых усилий, но есть и другие хорошие серверы сборки. Это действительно должно быть вашим решением.

Если вы по какой-то причине настаиваете, что можно распространять сборки для разработчиков среди клиентов;), тогда вам понадобится локальное решение.

Довольно простым решением было бы использование встроенной поддержки для автоматического увеличения номера сборки сборки:

// major.minor.build.revision
[assembly:AssemblyVersion("1.2.*")]

* выполняет автоматическое увеличение номера сборки при каждой компиляции (и есть изменения). Номер ревизии является случайным числом. Отсюда вы можете отслеживать связь с идентификатором Mercurial, сохраняя обе части информации, например, опубликовав его в каком-либо внутреннем веб-решении или в соответствии с вашими потребностями, или обновите созданную сборку. Я подозреваю, что вы можете использовать PostSharp или Mono.Cecil переписать сборку, например. путем исправления номера ревизии в качестве идентификатора. Если ваши сборки подписаны, перезапись должна произойти до того, как вы подпишете их, что немного утомительно, если у вас нет файла сборки. Обратите внимание, что VS можно настроить для компиляции с использованием пользовательского файла сборки вместо процедуры сборки по умолчанию.

Мое последнее предложение - создать отдельный проект только для идентификатора hg и использовать шаг после сборки, чтобы объединить созданные сборки в один. ILMerge поддерживает повторное подписание подписанных сборок, и поэтому, вероятно, будет легче сделать работу. Недостатком является то, что перераспределение ILMerge не допускается (хотя коммерческое использование разрешено).

Это не решение, но, надеюсь, вдохновение, чтобы вы начали.

...