Подтвердить что ты не робот

Внедрение информации о пересмотре Mercurial в проектах Visual Studio С#

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

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

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

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

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

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

В настоящий момент для каждого решения Visual Studio я добавляю следующие команды командной строки "Pre-build event":

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 =

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

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

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

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

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

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

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

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

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

Отклоненные решения этих задач

Прежде чем я применил текущее решение, я посмотрел на расширение Mercuryial Keyword, поскольку это казалось очевидным решением. Тем не менее, чем больше я смотрел на него и читал мнения людей, тем больше я пришел к выводу, что это не совсем то, что нужно делать.

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

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

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

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


Update

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

Поиграв с ним некоторое время, мне удалось получить файл 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 все еще настаивает на компиляции всего раз. Я пробовал искать решения и проверял "Только создавать проекты запуска и зависимости от Run" в "Инструменты | Параметры | Проекты и решения" | Сборка и запуск, но это не имеет никакого значения.

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

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

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

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

4b9b3361

Ответ 1

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

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

Вот шаги:

  • Получить MBBuildExtension Pack ИЛИ Запись пользовательской задачи для перезаписи AssemblyInfo.cs
  • Создать пользовательский Создайте задачу в своем собственном проекте, чтобы получить 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 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 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 фиксировано относительно каждого проекта, который вам нужно использовать он в. Таким образом, никто не должен ничего устанавливать.

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

Оди

Ответ 2

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

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

http://versioning.codeplex.com

Ответ 3

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

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

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

Ответ 4

Im my.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 $";

Ответ 5

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

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

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

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

Мы легко справлялись с использованием файлов cruisecontrol.net и msbuild.

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

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

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

Ответ 6

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

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

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

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

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

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

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

Ответ 7

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

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

По умолчанию версия заканчивается на 9999.9999, и мы разделили ревизию таким образом, что версия 12345678 станет 1234.5678 (не то, чтобы мы находились где-то рядом с 12-миллионной версией...).

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

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

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