自动在Visual Studio c#项目中嵌入mercurial修订信息

Mar*_*oth 54 c# mercurial build visual-studio-2008

原始问题

在构建我们的项目时,我希望将每个存储库的mercurial id嵌入到该存储库(库,应用程序或测试应用程序)的产品中.

我发现,如果您确切地知道构建他们正在使用的特定应用程序版本的内容,那么调试客户运行的应用程序将更加容易8个时区.因此,我们系统中的每个项目(应用程序或库)都实现了获取相关修订信息的方法.

我还发现能够查看应用程序是否已使用存储库中的干净(未修改)更改集进行编译非常有用.当存储库中存在未提交的更改时,'Hg id'会有用地在变更集ID中附加一个+,这样我们就可以轻松查看人们是否运行了干净或修改过的代码版本.

我目前的解决方案详述如下,并满足基本要求,但存在许多问题.

现行解决方案

目前,对于每个Visual Studio解决方案,我添加了以下"预构建事件命令行"命令:

cd $(ProjectDir)
HgID
Run Code Online (Sandbox Code Playgroud)

我还将HgID.bat文件添加到Project目录:

@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
Run Code Online (Sandbox Code Playgroud)

以及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 =
Run Code Online (Sandbox Code Playgroud)

当我建立我的应用程序时,将触发对所有库预生成事件,创建一个新的文件HgId.cs(没有版本控制下保持),并导致库重新编译与新的"汞ID" '版本'中的字符串.

当前解决方案的问题

主要问题是,由于HgId.cs是在每次预构建时重新创建的,因此每次我们需要编译任何内容时,都会重新编译当前解决方案中的所有项目.由于我们希望能够轻松地调试到我们的库中,因此我们通常会在主应用程序解决方案中引用许多库.这可能导致构建时间明显长于我想要的时间.

理想情况下,我希望只有在HgId.cs文件的内容实际更改时才编译库,而不是使用完全相同的内容重新创建.

这种方法的第二个问题是它依赖于windows shell的特定行为.我已经不得不多次修改批处理文件了,因为原版在XP下工作但不是Vista,下一个版本在Vista下工作但不是XP,最后我设法使它兼有.然而,随着时间的推移它是否适用于Windows 7,我认为承包商更有可能希望能够在他们的Windows 7盒子上构建我们的应用程序.

最后,我对这个解决方案有一个美学问题,批处理文件和一起模板文件感觉就像这样做的错误方法.

我的实际问题

您如何解决/如何解决我想要解决的问题?

有什么比我现在做的更好的选择?

拒绝解决这些问题

在我实现当前解决方案之前,我查看了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
Run Code Online (Sandbox Code Playgroud)

此解决方案存在问题

尽管每次都不再重新创建HgId.cs,但Visual Studio仍然坚持每次编译所有内容.我已经尝试寻找解决方案,并尝试在工具|选项|项目和解决方案|构建和运行中检查"仅构建启动项目和运行依赖项",但它没有任何区别.

第二个问题仍然存在,现在我无法测试它是否适用于Vista,因为该承包商不再与我们合作.

  • 如果有人可以在Windows 7和/或Vista盒子上测试这个批处理文件,我将不胜感激.

最后,我对此解决方案的美学问题甚至比以前更强,因为批处理文件更复杂,现在更容易出错.

如果您能想到更好的解决方案,我很乐意听到它们.

Joe*_*ley 25

我刚刚发布了一个小型的开源MSBuild任务来完成你所需要的:

  • 它将您的Mercurial修订版号放入.NET程序集版本中
  • 您可以从版本中判断是否已使用未提交的更改编译了程序集
  • 如果修订未更改,则不会导致不必要的构建
  • 不依赖于Windows脚本
  • 无需安装 - 只需在解决方案中添加一个小DLL,然后编辑项目中的一些文件即可

http://versioning.codeplex.com

  • 我喜欢这个想法,但对于mercurial,我想使用变更集的160位标识符,而不仅仅是修订号(这是一个int),因为它在存储库之间不一致. (5认同)
  • 这很酷.我已经开始在一个项目中使用它,设置起来轻而易举.谢谢乔. (2认同)

Aud*_*die 16

我想我有一个答案.这将涉及一些,但它让你远离必须做任何批处理文件.您可以依靠MSBuild和自定义任务为您执行此操作.我已经使用了MSBuild的扩展包(在CodePlex上可用) - 但你需要的第二个任务是你可以轻松自己写的东西.

使用此解决方案,您可以右键单击DLL并在文件属性中查看DLL(或EXE)来自的Mercurial版本.

以下是步骤:

  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;

        }
    }
Run Code Online (Sandbox Code Playgroud)

修改后的项目文件:(评论可能有帮助)

<?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>
Run Code Online (Sandbox Code Playgroud)

最后注意:构建任务项目只需要构建一次.每次执行其余解决方案时都不要尝试构建它.如果你这样做,你会发现VS2008锁定了DLL.尚未想出那个,但我认为更好的办法是按照你的意愿构建dll,然后只用你的代码分发dll,确保dll的位置相对于你需要使用的每个项目都是固定的它.这样,没有人必须安装任何东西.

祝你好运,我希望这有帮助!

奥迪


dth*_*rpe 5

您是否考虑过使用字符串资源而不是C#语言字符串常量?可以使用用于本地化的工具在构建后的输出二进制文件中编辑/替换字符串资源.

您可以将您的mercurial版本号发送到C#build未使用的文本文件,然后使用构建后操作将版本资源替换为发出的文本文件中的实际值.如果您对程序集进行强名称签名,则需要在签名之前进行资源字符串替换.

这就是我们多年前在Borland为Windows产品处理此问题的方法.从那时起,世界变得更加复杂,但这一原则仍然适用.