programing

Visual Studio C # 프로젝트에 자동으로 수은 개정 정보 포함

sourcetip 2021. 1. 15. 20:26
반응형

Visual Studio C # 프로젝트에 자동으로 수은 개정 정보 포함


원래 문제

프로젝트를 빌드 할 때 각 저장소의 수은 ID가 해당 저장소 (라이브러리, 애플리케이션 또는 테스트 애플리케이션)의 제품에 포함되기를 원합니다.

고객이 사용중인 특정 버전의 애플리케이션을 빌드하는 데 무엇이 들어 갔는지 정확히 알고 있다면 8 시간대 떨어진 고객이 실행하는 애플리케이션을 훨씬 쉽게 디버깅 할 수 있습니다. 따라서 시스템의 모든 프로젝트 (애플리케이션 또는 라이브러리)는 관련 개정 정보를 가져 오는 방법을 구현합니다.

또한 리포지토리에서 수정되지 않은 깨끗한 변경 집합으로 응용 프로그램이 컴파일되었는지 확인할 수 있다는 점도 매우 유용합니다. 'Hg id'는 저장소에 커밋되지 않은 변경 사항이있을 때 변경 집합 ID에 +를 유용하게 추가하므로 사람들이 코드의 깨끗한 버전 또는 수정 된 버전을 실행 중인지 쉽게 확인할 수 있습니다.

내 현재 솔루션은 아래에 자세히 설명되어 있으며 기본 요구 사항을 충족하지만 많은 문제가 있습니다.

현재 솔루션

현재 모든 Visual Studio 솔루션에 다음 "사전 빌드 이벤트 명령 줄"명령을 추가합니다.

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 =

내 애플리케이션을 빌드 할 때 모든 라이브러리에서 사전 빌드 이벤트가 트리거되어 새 HgId.cs 파일 (리비전 제어하에 유지되지 않음)이 생성되고 라이브러리가 새 'hg id'로 다시 컴파일됩니다. '버전'의 문자열입니다.

현재 솔루션의 문제점

주요 문제는 HgId.cs가 각 사전 빌드에서 다시 생성되기 때문에 컴파일해야 할 때마다 현재 솔루션의 모든 프로젝트가 다시 컴파일된다는 것입니다. 라이브러리로 쉽게 디버그 할 수 있기를 원하기 때문에 일반적으로 메인 애플리케이션 솔루션에서 참조되는 많은 라이브러리를 유지합니다. 이로 인해 내가 원하는 것보다 훨씬 더 긴 빌드 시간이 발생할 수 있습니다.

이상적으로는 HgId.cs 파일의 내용이 정확히 동일한 내용으로 다시 생성되는 것과 달리 실제로 변경된 경우에만 라이브러리를 컴파일하고 싶습니다.

이 방법의 두 번째 문제는 Windows 셸의 특정 동작에 대한 의존성입니다. 원본은 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는 매번 모든 것을 컴파일해야한다고 주장합니다. 나는 솔루션을 찾아 보았고 Tools | Options | Projects | Build and Run에서 "Only build startup projects and dependencies on Run"을 확인했지만 차이가 없습니다.

두 번째 문제도 여전히 남아 있으며, 해당 계약자가 더 이상 우리와 함께 있지 않기 때문에 Vista에서 작동하는지 테스트 할 방법이 없습니다.

  • 누구든지 Windows 7 및 / 또는 Vista 상자에서이 배치 파일을 테스트 할 수 있다면 어떻게되었는지 듣고 싶습니다.

마지막으로,이 솔루션의 미적 문제는 이전보다 훨씬 더 강력합니다. 배치 파일이 더 복잡하고 이제 잘못 될 것이 더 많기 때문입니다.

더 나은 솔루션을 생각할 수 있다면 그에 대해 듣고 싶습니다.


나는 당신에게 답을 가지고 있다고 생각합니다. 이것은 약간 복잡 할 것이지만 배치 파일을 수행 할 필요가 없습니다. MSBuild 및 사용자 지정 작업을 사용하여이 작업을 수행 할 수 있습니다. MSBuild ( CodePlex 에서 사용 가능) 용 확장 팩을 사용 했지만 두 번째로 필요한 작업은 직접 작성할 수있는 작업입니다.

이 솔루션을 사용하면 DLL을 마우스 오른쪽 버튼으로 클릭하고 파일 속성에서 DLL (또는 EXE)의 출처 인 Mercurial 버전을 확인할 수 있습니다.

단계는 다음과 같습니다.

  1. 가져 오기 MBBuildExtension 팩 또는 덮어 쓰기를 AssemblyInfo.cs에 쓰기 사용자 지정 작업을
  2. 자체 프로젝트에서 사용자 지정 빌드 작업을 만들어 Mercurial ID (아래 코드)를 가져옵니다.
  3. 사용자 지정 작업을 사용하려면 Mercurial ID가 필요한 프로젝트 파일을 편집합니다 (아래 코드).

수은 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>

Last Note: The build tasks project only needs to be built once. Don't try to build it every time you do the rest of your solution. If you do, you will find that VS2008 has the dll locked. Haven't figured that one out yet, but I think the better thing to do is build the dll as you want it, then distribute ONLY the dll with your code, ensuring that the dll's location is fixed relative to every project you need to use it in. That way, no one has to install anything.

Good luck, and I hope this helps!

Audie


I've just released a small open-source MSBuild task to do exactly what you need:

  • It puts your Mercurial revision number into your .NET assembly version
  • You can tell from the version if an assembly has been compiled with uncommitted changes
  • Does not cause unnecessary builds if the revision hasn't changed
  • Not dependent on Windows scripting
  • Nothing to install - you just add a small DLL to your solution, and edit some files in your project

http://versioning.codeplex.com


Have you considered using a string resource instead of a C# language string constant? String resources can be edited/replaced in the output binaries post-build using tools intended for localization.

You would emit your mercurial version number to a text file that is not used by the C# build, then using a post-build operation replace the version resource with the actual value from the emitted text file. If you strong-name sign your assemblies, the resource string replacement would need to happen before the signing.

This is how we handled this issue at Borland years ago for Windows products. The world has become more complicated since then, but the principle still applies.


Im my .hgrc I have this:

[extensions]                                                                                                                                                               
hgext.keyword =                                                                                                                                                            
hgext.hgk =                                                                                                                                                                

[keyword]                                                                                                                                                                  
** =                                                                                                                                                                       

[keywordmaps]                                                                                                                                                              
Id = {file|basename},v {node|short} {date|utcdate} {author|user} 

And in my source file (Java) I do:

public static final String _rev = "$Id$";

After commit $Id$ gets expanded into something like:

public static final String _rev = "$Id: Communication.java,v 96b741d87d07 2010/01/25 10:25:30 mig $";

We have solved this issue with another source control system, subversion.

What we do is that we have a commonassemblyinfo.cs file and we plug the svn revision number into that using an msbuild script.

We literally call svninfo and scrape the output from the revision: part and then poke it into the CommonAssemblyInfo.cs file.

Every project in our solution has a link to the common file and is then compiled, meaning that the assemblies are versioned on compilation with the svn revision number, it also means all dependent libraries we have written are also versioned.

We acheived this quite easily using cruisecontrol .net and msbuild files.

I have not used mercurial but I beleive you could do something similar with the id command ? but because of the format of the output you would need to approach how you used it somewhat differently.

I would have a standard xml file that is read on app startup and you could poke the information into that (works for resx files also). You can then read that value back out in code. A bit icky but it works.

My favourite solution is the way we do it but you can't do that in mercurial as the changeset info is not purely numeric like the svn revision number is.


There seems to be multiple possible approaches to this problem. The first and probably preferred solution would be to install a build server and only distribute builds generated there to customers. This has the advantage that you never ship uncommitted changes. By using MSBuild, NAnt or some other task-based build tool the entire process is very flexible. I was able to install TeamCity and get the first couple of builds up and running with very little effort, but there are other good build servers too. This really should be your solution.

If you for some reason insist that it's okay to distribute developer builds to clients ;) then you'll need a local solution.

A fairly easy solution would be to use the built-in support for auto-incrementing the build number of an assembly:

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

The * makes the build number auto-increment every time you compile (and there are changes). The revision number is a random number. From here you can either keep track of the association to the Mercurial id by saving both pieces of information, e.g. by posting it to some internal web solution or whatever fits your particular needs, or update the generated assembly. I'd suspect you could use PostSharp or Mono.Cecil do rewrite the assembly, e.g. by patching the revision number to be the id. If your assemblies are signed the rewrite needs to happen before you sign them, which is a bit bothersome if you don't have a build file. Note that VS can be configured to compile using your custom build file instead of the default build procedure.

My final suggestion is to create a separate project just for the hg id, and use the post-build step to merge the generated assemblies into one. ILMerge supports re-signing of signed assemblies and this is therefore likely to be easier to make work. The downside is that redistribution of ILMerge is not allowed (although commercial use is).

It's not a solution but hopefully inspiration to get you going.


Here's what we do here: we do not embed the revision every time the project is built on developer's machines. At best this causes the problems you've mentioned above, and at worst it's misleading because you could have modified files in your workspace.

Instead we only embed the source control revision number when our projects are built under TeamCity on a separate build server. The revision is embedded into both AssemblyVersion and AssemblyFileVersion, split across the last two parts.

By default the version ends in 9999.9999, and we split the revision in such a way that revision 12345678 would become 1234.5678 (not that we're anywhere close to the 12 millionth revision...).

This process guarantees that a product whose version is 2.3.1.1256 was definitely a pristine build of revision 11,256. Anything developers build manually will instead look like this: 2.3.9999.9999.

The exact details of how the above is achieved are not directly relevant since we're not using Mercurial, but briefly: TeamCity handles checking out the required revision and passes its number to our MSBuild script, which then does the rewriting of AssemblyInfo.cs with the help of a custom task we wrote.

What I particularly like about this is that absolutely no files are modified by hitting F5 in Visual Studio - in fact, as far as Visual Studio is concerned, it's working with a plain old normal solution.

ReferenceURL : https://stackoverflow.com/questions/2386440/embedding-mercurial-revision-information-in-visual-studio-c-sharp-projects-autom

반응형