草庐IT

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

coder 2023-04-24 原文

原问题

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

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

我还发现能够查看应用程序是否已使用存储库中的干净(未修改)变更集进行编译非常有用。当存储库中有未提交的更改时,'Hg id' 有用地将 + 附加到变更集 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”重新编译库“版本”中的字符串。

当前解决方案的问题

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

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

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

最后,我对这个解决方案有一个美学问题,批处理文件和拼凑在一起的模板文件感觉是错误的方法。

我的实际问题

你将如何解决/你如何解决我试图解决的问题?

有什么比我目前正在做的更好的选择?

拒绝这些问题的解决方案

在我实现当前的解决方案之前,我查看了 Mercurials 关键字扩展,因为它似乎是显而易见的解决方案。然而,我越看它并阅读人们的意见,我就越得出结论,这不是正确的做法。

我还记得关键字替换在以前公司的项目中给我带来的问题(一想到不得不再次使用 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 仍然坚持每次都编译所有内容。我尝试寻找解决方案并尝试在工具|选项|项目和解决方案|构建和运行中检查“仅构建启动项目和运行依赖项”,但没有任何区别。

第二个问题仍然存在,现在我无法测试它是否适用于 Vista,因为该承包商不再与我们在一起。
  • 如果有人可以在 Windows 7 和/或 Vista 机器上测试这个批处理文件,我会很感激听到它是如何进行的。

  • 最后,我对这个解决方案的审美问题比以前更严重,因为批处理文件更复杂,而且现在还有更多问题。

    如果您能想到任何更好的解决方案,我很乐意听取他们的意见。

    最佳答案

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

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

    以下是步骤:

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

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

    奥迪

    关于c# - 自动在 Visual Studio c# 项目中嵌入 mercurial 修订信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2386440/

    有关c# - 自动在 Visual Studio c# 项目中嵌入 mercurial 修订信息的更多相关文章

    1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    2. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

      大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

    3. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

      如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

    4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    5. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

      我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

    6. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

      我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

    7. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

      我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

    8. c# - 如何在 ruby​​ 中调用 C# dll? - 2

      如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

    9. Ruby 从大范围中获取第 n 个项目 - 2

      假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

    10. C# 到 Ruby sha1 base64 编码 - 2

      我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

    随机推荐