在Visual Studio c#项目中自动embeddedmercurial修订信息
原始问题
在构build我们的项目时,我希望将每个存储库的mercurial idembedded到该存储库(库,应用程序或testing应用程序)的产品中。
我发现如果您确切地知道构build他们正在使用的应用程序的特定版本是什么,那么debugging由客户8时区以外的应用程序将更容易。 因此,我们系统中的每个项目(应用程序或库)都实现了获取相关修订信息的方法。
我也发现能够查看应用程序是否已经使用来自存储库的干净的(未修改的)变更集进行编译是非常有用的。 当存储库中存在未提交的更改时,“Hg id”有用地将+添加到变更集标识,因此,这使我们可以轻松查看人们是否正在运行干净的或修改的代码版本。
我现在的解决scheme在下面有详细介绍,并且满足了基本的要求,但是它有一些问题。
当前解决scheme
目前,对于每个Visual Studio解决scheme,我添加以下“预生成事件命令行”命令:
cd $(ProjectDir) HgID
我还将一个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
以及一个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 =
当我构build我的应用程序时,预构build事件在所有库上触发,创build一个新的HgId.cs文件(不保存在版本控制下),并使库重新编译为新的“hg id” “版本”中的string。
目前的解决scheme的问题
主要的问题在于,由于HgId.cs是在每次预编译时重新创build的,因此每次我们需要编译任何东西时,当前解决scheme中的所有项目都会重新编译。 由于我们希望能够轻松地debugging到我们的库中,我们通常在我们的主应用程序解决scheme中保留很多库。 这可能会导致构build时间比我想要的要长得多。
理想情况下,我希望只有当HgId.cs文件的内容实际上已经改变时才能编译库,而不是用完全相同的内容重新创build。
这个方法的第二个问题是它依赖于windows shell的特定行为。 我已经多次修改了batch file,因为原来在XP下工作,但不是Vista,下一个版本在Vista下工作,但不是XP,最后我设法使它与两个工作。 不pipe它能与Windows 7一起工作,但是随着时间的推移,我认为承包商可能希望能够在他们的Windows 7 boxen上构build我们的应用程序。
最后,我有这个解决scheme的美学问题,batch file和模板文件一起感觉就像做错了。
我的实际问题
你将如何解决/你如何解决我想解决的问题?
有什么比我目前正在做的更好的select?
拒绝这些问题的解决scheme
在我实施目前的解决scheme之前,我查看了Mercurials关键字扩展,因为它看起来像是一个明显的解决scheme。 然而,我越是看着人民的意见,就越是认为这是不对的。
我还记得关键字replace在以前的公司项目中给我造成的问题(只是想到再次使用Source Safe会让我感到恐惧* 8)。
此外,我不特别想要启用Mercurial扩展来完成构build。 我希望解决scheme是自包含的,因此,仅仅因为没有启用扩展或者没有安装右侧的帮助程序软件,没有embedded的版本信息就意外编译应用程序并不容易。
我还想到用一种更好的脚本语言编写这个脚本语言, 如果内容真的改变了,我只能写一个HgId.cs文件,但是我能想到的所有选项都需要我的同事,承包商和可能的客户必须安装他们可能不需要的软件(例如cygwin)。
任何人可以想到的其他选项将不胜感激。
更新
部分解决scheme
已经玩了一段时间,我已经设法让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
这个解决scheme的问题
即使HgId.cs不再每次都被重新创build,Visual Studio仍然坚持每次编译一切。 我已经尝试寻找解决scheme,并尝试在工具|选项|项目和解决scheme|构build和运行中检查“在运行时只构build启动项目和依赖关系”,但没有任何区别。
第二个问题也依然存在,现在我无法testing它是否能与Vista合作,因为这个承包商已经不在我们这边了。
- 如果任何人都可以在Windows 7和/或Vista上testing这个batch file,我将不胜感激。
最后,这个解决scheme的审美问题比以前更加强烈,因为batch file比较复杂,现在出现了更多错误。
如果你能想出更好的解决scheme,我很乐意听到他们的消息。
我想我有一个答案给你。 这将有点牵扯,但它使你不必做任何batch file。 你可以依靠MSBuild和自定义任务来为你做这个。 我已经使用了MSBuild的扩展包(可在CodePlex上使用 ),但是您需要的第二项任务是您可以轻松编写自己的任务。
使用此解决scheme,您可以右键单击该DLL,并在文件属性中查看DLL(或EXE)来自哪个Mercurial版本。
这里是步骤:
- 获取MBBuildExtension包 或编写自定义任务来覆盖AssemblyInfo.cs
- 在自己的项目中创build一个自定义生成任务来获取Mercurial Id(代码如下)。
- 编辑需要使用自定义任务的Mercurial Id的项目文件(代码如下)。
自定义任务获取mercurial id 🙁这需要进行testing,也许更好的泛化…)
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>
上次注意:构build任务项目只需要构build一次。 每当你做其余的解决scheme时,不要试图build立它。 如果你这样做,你会发现VS2008的dll被locking。 还没有弄清楚,但我认为最好的办法是build立你想要的DLL,然后只分发你的代码的DLL,确保DLL的位置相对于你需要使用的每个项目是固定的这样,没有人需要安装任何东西。
祝你好运,我希望这有助于!
奥迪
我刚刚发布了一个小的开源MSBuild任务,以完成您所需要的任务:
- 它把你的Mercurial修订号码放到你的.NET程序集版本中
- 您可以从版本中知道程序集是否已经编译了未提交的更改
- 如果修订没有改变,不会导致不必要的构build
- 不依赖于Windows脚本
- 没有什么可以安装的 – 你只需要在你的解决scheme中添加一个小的DLL,然后编辑你的项目中的一些文件
你有没有考虑使用string资源,而不是C#语言string常量? string资源可以使用本地化工具在编译后的输出二进制文件中进行编辑/replace。
您可以将您的mercurial版本号发送到未由C#构build使用的文本文件,然后使用后构build操作,使用发出的文本文件中的实际值replace版本资源。 如果你强壮的签名你的程序集,在签名之前需要进行资源stringreplace。
这就是我们在Borland多年前为Windows产品处理这个问题的方法。 从那时起,世界变得更加复杂,但是这个原则仍然适用。
我的.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 $";
我们已经用另一个源代码控制系统颠覆了这个问题。
我们所做的是我们有一个commonassemblyinfo.cs文件,我们将svn版本号插入到使用msbuild脚本的版本中。
我们从字面上调用svninfo,并从修订:部分刮取输出,然后将其戳到CommonAssemblyInfo.cs文件中。
我们解决scheme中的每个项目都有一个到公共文件的链接,然后进行编译,这意味着程序集在使用svn修订版编号进行编译时版本化,这也意味着我们编写的所有依赖库也都是版本化的。
我们使用cruisecontrol .net和msbuild文件轻松实现了这一点。
我没有使用mercurial,但我相信你可以做一些类似的id命令? 但是由于输出格式的缘故,你需要以不同的方式来使用它。
我将有一个标准的XML文件,在应用程序启动时读取,你可以戳到信息(也适用于resx文件)。 然后,您可以通过代码读取该值。 有点恶心,但它的作品。
我最喜欢的解决scheme是我们这样做的方式,但是你不能这样做,因为变更集信息不像svn修订版本号那样是纯数字的。
似乎有多种可能的方法来解决这个问题。 第一个也许是首选的解决scheme是安装一个构build服务器,并且只将在那里生成的构build分发给客户。 这样做的好处是您永远不会发送未提交的更改。 通过使用MSBuild,NAnt或其他基于任务的构build工具,整个过程非常灵活。 我能够安装TeamCity,只花费很less的精力即可获得第一批的构build和运行,但也有其他的优秀构build服务器。 这真的应该是你的解决scheme。
如果您因为某种原因坚持认为可以将开发人员版本分发给客户端,那么您需要一个本地解决scheme。
一个相当简单的解决scheme是使用内置支持来自动递增程序集的内部版本号:
// major.minor.build.revision [assembly:AssemblyVersion("1.2.*")]
*
使编译编号自动增量每次你编译(和有变化)。 版本号是一个随机数。 从这里,您可以通过保存两条信息(例如,将其发布到某个内部Web解决scheme或适合您的特定需求的任何内容)来更新生成的程序集,从而跟踪与Mercurial ID的关联。 我怀疑你可以使用PostSharp或Mono.Cecil重写程序集,例如修补版本号为id。 如果您的程序集已经签名,那么在签名前需要重写,如果您没有构build文件,这有点麻烦。 请注意,VS可以configuration为使用您的自定义构build文件而不是默认构build过程进行编译。
我最后的build议是为hg id创build一个单独的项目,并使用生成后步骤将生成的程序集合并为一个。 ILMerge支持对已签名的程序集进行重新签名,因此可能更容易进行工作。 缺点是不允许重新分配ILMerge(虽然是商业用途)。
这不是一个解决scheme,但希望启发你去。
以下是我们在这里所做的:每当在开发人员的机器上构build项目时,我们都不会embedded修订版本。 最好这会导致上面提到的问题,最坏的情况是误导,因为您可能已经修改了工作区中的文件。
相反,我们只在项目在TeamCity下的独立构build服务器上构build时才embedded源代码pipe理修订版本号。 该版本embedded到AssemblyVersion
和AssemblyFileVersion
,分为最后两部分。
默认情况下,版本以9999.9999结尾,我们将修订版本修改为12345678修改为1234.5678(不是我们接近第1200万修订版)。
这个过程保证了一个版本为2.3.1.1256的产品绝对是一个11,256版本的原始版本。 开发人员手动构build的任何内容都将如下所示:2.3.9999.9999。
如何实现上面的具体细节并不直接相关,因为我们没有使用Mercurial,而是简单地说:TeamCity处理检查所需的修订,并将其编号传递给我们的MSBuild脚本,然后用它重写AssemblyInfo.cs我们写的一个自定义任务的帮助。
我特别喜欢的是,在Visual Studio中绝对没有文件被F5修改 – 事实上,就Visual Studio而言,它正在使用一个普通的常规解决scheme。