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

时间:2010-03-05 11:53:24

标签: c# visual-studio-2008 mercurial build

7 个答案:

答案 0 :(得分:25)

我刚刚发布了一个小型的开源MSBuild任务,可以完全满足您的需求:

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

http://versioning.codeplex.com

答案 1 :(得分:16)

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

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

以下是步骤:

  1. 获取MBBuildExtension Pack OR 编写自定义任务以覆盖 AssemblyInfo.cs
  2. 创建自定义 在自己的项目中构建任务来获取 Mercurial Id(下面的代码)。
  3. 编辑需要的项目文件 Mercurial Id使用自定义任务 (以下代码)。
  4. 获取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的位置相对于你需要使用的每个项目都是固定的它。这样,没有人必须安装任何东西。

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

    奥迪

答案 2 :(得分:5)

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

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

这就是我们多年前在Borland为Windows产品处理此问题的方法。从那以后世界变得越来越复杂,但这个原则仍然适用。

答案 3 :(得分:2)

我的.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 $";

答案 4 :(得分:2)

我们用另一个源控制系统subversion解决了这个问题。

我们做的是我们有一个commonassemblyinfo.cs文件,我们使用msbuild脚本将svn修订版号插入其中。

我们实际上调用svninfo并从revision:part中删除输出,然后将其戳入CommonAssemblyInfo.cs文件中。

我们解决方案中的每个项目都有一个指向公共文件的链接,然后进行编译,这意味着程序集在编译时使用svn版本号进行版本控制,这也意味着我们编写的所有依赖库也都是版本化的。

我们使用cruisecontrol .net和msbuild文件很容易实现这一点。

我没有使用mercurial,但我相信你可以用id命令做类似的事情? 但是由于输出的格式,你需要接近你使用它的方式有所不同。

我会在应用启动时读取一个标准的xml文件,你可以将信息戳入其中(也适用于resx文件)。然后,您可以在代码中读取该值。有点icky但它​​的确有效。

我最喜欢的解决方案是我们这样做的方式,但你不能在mercurial中这样做,因为变更集信息不像svn修订版号那样纯数字。

答案 5 :(得分:1)

这个问题似乎有多种可能的方法。第一个也许是首选的解决方案是安装构建服务器,并仅将生成的构建分发给客户。这样做的好处是您永远不会发送未提交的更改。通过使用MSBuild,NAnt或其他一些基于任务的构建工具,整个过程非常灵活。我能够安装TeamCity,并且只需很少的努力即可获得第一批构建和运行,但也有其他良好的构建服务器。这真的应该是你的解决方案。

如果您出于某种原因坚持将开发者版本分发给客户端是可以的;)那么您将需要一个本地解决方案。

一个相当简单的解决方案是使用内置支持来自动增加程序集的内部版本号:

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

每次编译时*都会使内部版本号自动增加(并且有更改)。修订号是随机数。从这里,您可以通过保存两条信息来跟踪与Mercurial id的关联,例如:通过将其发布到某些内部Web解决方案或满足您特定需求的任何内容,或更新生成的程序集。我怀疑你可以使用PostSharp或Mono.Cecil重写程序集,例如通过修补修订号作为id。如果您的程序集已签名,则需要在签名之前进行重写,如果您没有构建文件,这有点麻烦。请注意,VS可以配置为使用您的自定义生成文件而不是默认生成过程进行编译。

我的最后建议是为hg id创建一个单独的项目,并使用post-build步骤将生成的程序集合并为一个。 ILMerge支持重新签名已签名的程序集,因此可能更容易完成工作。缺点是不允许重新分配ILMerge(虽然是商业用途)。

这不是一个解决方案,但希望能让你前进的灵感。

答案 6 :(得分:1)

以下是我们在此处所做的事情:每次在开发人员的计算机上构建项目时,我们都不会嵌入修订版。充其量这会导致您上面提到的问题,最糟糕的是它会产生误导,因为您可能在工作区中修改了文件。

相反,当我们的项目在TeamCity下在单独的构建服务器上构建时,我们只嵌入了源代码控制修订版号。修订版嵌入AssemblyVersionAssemblyFileVersion,分为最后两部分。

默认情况下,版本以9999.9999结尾,我们以修订版12345678将成为1234.5678的方式拆分修订版(不是我们接近第1200万次修订版......)。

此过程可确保版本为2.3.1.1256的产品绝对是原始版本的修订版11,256。开发人员手动构建的任何内容都将如下所示:2.3.9999.9999。

上述方法的确切细节并不直接相关,因为我们没有使用Mercurial,而是简单地说:TeamCity处理检出所需的修订并将其编号传递给我们的MSBuild脚本,然后重写AssemblyInfo .cs在我们编写的自定义任务的帮助下。

我特别喜欢的是,在Visual Studio中点击F5绝对没有文件被修改 - 事实上,就Visual Studio而言,它正在使用一个普通的普通解决方案。