web-dev-qa-db-ja.com

ビルド日を表示する

現在、タイトルウィンドウにビルド番号を表示するアプリがあります。それは、最新のビルドを持っているかどうかを知りたいと思うほとんどのユーザーにとって何も意味がないことを除いて、うまくいきます-ビルド1.0.8.4321ではなく「先週の木曜日」と呼ぶ傾向があります。

代わりにビルド日付をそこに置く計画です-たとえば「2009年10月21日にビルドされたアプリ」。

このように使用するためのテキスト文字列としてビルド日付を引き出​​すプログラム的な方法を見つけるのに苦労しています。

ビルド番号には、次のものを使用しました。

Assembly.GetExecutingAssembly().GetName().Version.ToString()

それらがどのように生まれたかを定義した後。

コンパイル日(およびボーナスポイントの時間)については、このようなものが欲しいです。

ここでのポインターは非常に高く評価されています(適切な場合はしゃれを許す)、またはよりきれいなソリューション...

240
Mark Mayo

ジェフ・アトウッドは、この問題について ビルド日を決定するのが難しい方法 でいくつか述べました。

最も信頼性の高い方法は、実行可能ファイルに埋め込まれている PEヘッダー からリンカーのタイムスタンプを取得することであることがわかりました。そのためのコメント(Jeffの記事)に対するC#コード(Joe Spiveyによる):

public static DateTime GetLinkerTime(this Assembly assembly, TimeZoneInfo target = null)
{
    var filePath = Assembly.Location;
    const int c_PeHeaderOffset = 60;
    const int c_LinkerTimestampOffset = 8;

    var buffer = new byte[2048];

    using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        stream.Read(buffer, 0, 2048);

    var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset);
    var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset);
    var Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    var linkTimeUtc = Epoch.AddSeconds(secondsSince1970);

    var tz = target ?? TimeZoneInfo.Local;
    var localTime = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tz);

    return localTime;
}

使用例:

var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();

更新:メソッドは.Net Core 1.0で機能していましたが、。Net Core 1.1リリース後に機能を停止しました(1900-2020の範囲でランダムな年を与えます)

348
mdb

ビルド前のイベントコマンドラインに以下を追加します。

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

このファイルをリソースとして追加すると、リソースに「BuildDate」文字列が含まれるようになります。

リソースを作成するには、 。NETでリソースを作成および使用する方法 を参照してください。

87
Abdurrahim

comments の@ c00000fdで指摘されているように。 Microsoftはこれを変更しています。そして、多くの人がコンパイラの最新バージョンを使用していない一方で、この変更がこのアプローチを間違いなく悪いものにしていると思います。それは楽しい演習ですが、バイナリ自体のビルド日付を追跡することが重要な場合は、必要な他の手段でビルド日付をバイナリに埋め込むことをお勧めします。

これは、おそらくビルドスクリプトの最初のステップである、簡単なコード生成で行うことができます。それに加えて、ALM/Build/DevOpsツールはこれに非常に役立ち、他のものよりも優先されるべきです。

この回答の残りは、歴史的な目的のためだけに残しておきます。

新しい方法

私はこれについて考えを変え、現在このトリックを使用して正しいビルド日付を取得しています。

#region Gets the build date and time (by reading the COFF header)

// http://msdn.Microsoft.com/en-us/library/ms680313

struct _IMAGE_FILE_HEADER
{
    public ushort Machine;
    public ushort NumberOfSections;
    public uint TimeDateStamp;
    public uint PointerToSymbolTable;
    public uint NumberOfSymbols;
    public ushort SizeOfOptionalHeader;
    public ushort Characteristics;
};

static DateTime GetBuildDateTime(Assembly assembly)
{
    var path = Assembly.GetName().CodeBase;
    if (File.Exists(path))
    {
        var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
        using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
        {
            fileStream.Position = 0x3C;
            fileStream.Read(buffer, 0, 4);
            fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
            fileStream.Read(buffer, 0, 4); // "PE\0\0"
            fileStream.Read(buffer, 0, buffer.Length);
        }
        var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));

            return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond));
        }
        finally
        {
            pinnedBuffer.Free();
        }
    }
    return new DateTime();
}

#endregion

古い方法

さて、どのようにビルド番号を生成しますか? AssemblyVersion属性をe.g.などに変更すると、Visual Studio(またはC#コンパイラ)は実際に自動ビルド番号とリビジョン番号を提供します。 1.0.*

何が起こるかというと、ビルドは現地時間の2000年1月1日からの日数に等しくなり、リビジョンは現地時間の午前0時からの秒数を2で割った値に等しくなります。

コミュニティコンテンツを参照してください 自動ビルドおよびリビジョン番号

例えばAssemblyInfo.cs

[Assembly: AssemblyVersion("1.0.*")] // important: use wildcard for build and revision numbers!

SampleCode.cs

var version = Assembly.GetEntryAssembly().GetName().Version;
var buildDateTime = new DateTime(2000, 1, 1).Add(new TimeSpan(
TimeSpan.TicksPerDay * version.Build + // days since 1 January 2000
TimeSpan.TicksPerSecond * 2 * version.Revision)); // seconds since midnight, (multiply by 2 to get original)
86
John Leidegren

ビルド前のイベントコマンドラインに以下を追加します。

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

このファイルをリソースとして追加すると、リソースに「BuildDate」文字列が含まれるようになります。

ファイルをリソースに(パブリックテキストファイルとして)挿入した後、次の方法でアクセスしました

string strCompTime = Properties.Resources.BuildDate;

リソースを作成するには、 。NETでリソースを作成および使用する方法 を参照してください。

41
brewmanz

私が誰も言及していないことに驚いているアプローチの1つは、コード生成に T4 Text Templates を使用することです。

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ Assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ output extension=".g.cs" #>
using System;
namespace Foo.Bar
{
    public static partial class Constants
    {
        public static DateTime CompilationTimestampUtc { get { return new DateTime(<# Write(DateTime.UtcNow.Ticks.ToString()); #>L, DateTimeKind.Utc); } }
    }
}

長所:

  • ロケールに依存しない
  • コンパイル時よりもはるかに多くのことが可能

短所:

20
Peter Taylor

アセンブリPEヘッダーのバイトからビルド日付/バージョン情報を取得する手法に関して、MicrosoftはVisual Studio 15.4からデフォルトのビルドパラメーターを変更しました。新しいデフォルトには確定的なコンパイルが含まれ、有効なタイムスタンプと自動的にインクリメントされるバージョン番号が過去のものになります。タイムスタンプフィールドはまだ存在しますが、何かのハッシュである永続的な値で満たされますが、ビルド時間を示すものではありません。

http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.htmlここにいくつかの詳細な背景

確定的なコンパイルよりも有用なタイムスタンプを優先する場合、新しいデフォルトをオーバーライドする方法があります。次のように、対象のアセンブリの.csprojファイルにタグを含めることができます。

  <PropertyGroup>
      ...
      <Deterministic>false</Deterministic>
  </PropertyGroup>

更新:ここの別の回答で説明されているT4テキストテンプレートソリューションを推奨します。確定的なコンパイルの利点を失うことなく、問題をきれいに解決するために使用しました。それに関する1つの注意点は、ビルド時ではなく、.ttファイルの保存時にのみVisual StudioがT4コンパイラを実行することです。 .csの結果をソース管理から除外すると(生成されると予想されるため)、別の開発者がコードをチェックアウトする場合、これは厄介な場合があります。再保存しないと、.csファイルはありません。 T4コンパイルをすべてのビルドの一部にするnuget(AutoT4と呼ばれると思います)にはパッケージがあります。私は、実稼働展開中にこれに対する解決策にまだ直面していませんが、それを正しくするために類似した何かを期待しています。

16
Tom

私はC#初心者ですので、おそらく私の答えは愚かに聞こえます-実行可能ファイルが最後に書き込まれた日付からビルド日付を表示します:

string w_file = "MyProgram.exe"; 
string w_directory = Directory.GetCurrentDirectory();

DateTime c3 =  File.GetLastWriteTime(System.IO.Path.Combine(w_directory, w_file));
RTB_info.AppendText("Program created at: " + c3.ToString());

File.GetCreationTimeメソッドを使用しようとしましたが、奇妙な結果が得られました。コマンドの日付は2012-05-29でしたが、Window Explorerの日付は2012-05-23を示していました。この不一致を検索した後、ファイルはおそらく2012-05-23に作成され(Windowsエクスプローラーで表示)、2012-05-29で現在のフォルダーにコピーされた(File.GetCreationTimeコマンドで表示)ので、安全のために、File.GetLastWriteTimeコマンドを使用しています。

ザレック

12
Zalek Bloom

Windows 8/Windows Phone 8でコンパイル時間を取得する必要がある場合:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestamp(Assembly assembly)
    {
        var pkg = Windows.ApplicationModel.Package.Current;
        if (null == pkg)
        {
            return null;
        }

        var assemblyFile = await pkg.InstalledLocation.GetFileAsync(Assembly.ManifestModule.Name);
        if (null == assemblyFile)
        {
            return null;
        }

        using (var stream = await assemblyFile.OpenSequentialReadAsync())
        {
            using (var reader = new DataReader(stream))
            {
                const int PeHeaderOffset = 60;
                const int LinkerTimestampOffset = 8;

                //read first 2048 bytes from the Assembly file.
                byte[] b = new byte[2048];
                await reader.LoadAsync((uint)b.Length);
                reader.ReadBytes(b);
                reader.DetachStream();

                //get the pe header offset
                int i = System.BitConverter.ToInt32(b, PeHeaderOffset);

                //read the linker timestamp from the PE header
                int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);

                var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
                return dt.AddSeconds(secondsSince1970);
            }
        }
    }

Windows Phone 7でコンパイル時間を取得する必要がある場合:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestampAsync(Assembly assembly)
    {
        const int PeHeaderOffset = 60;
        const int LinkerTimestampOffset = 8;            
        byte[] b = new byte[2048];

        try
        {
            var rs = Application.GetResourceStream(new Uri(Assembly.ManifestModule.Name, UriKind.Relative));
            using (var s = rs.Stream)
            {
                var asyncResult = s.BeginRead(b, 0, b.Length, null, null);
                int bytesRead = await Task.Factory.FromAsync<int>(asyncResult, s.EndRead);
            }
        }
        catch (System.IO.IOException)
        {
            return null;
        }

        int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
        int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
        var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
        dt = dt.AddSeconds(secondsSince1970);
        return dt;
    }

注:すべての場合において、サンドボックスで実行しているため、アプリで展開するアセンブリのコンパイル時間のみを取得できます。 (つまり、これはGACの何に対しても機能しません)。

10
Matt Dotson

上記のメソッドは、アセンブリ用に微調整できますプロセス内に既にロードされていますメモリ内のファイルのイメージを使用して(ストレージから再読み込みするのではなく):

using System;
using System.Runtime.InteropServices;
using Assembly = System.Reflection.Assembly;

static class Utils
{
    public static DateTime GetLinkerDateTime(this Assembly assembly, TimeZoneInfo tzi = null)
    {
        // Constants related to the Windows PE file format.
        const int PE_HEADER_OFFSET = 60;
        const int LINKER_TIMESTAMP_OFFSET = 8;

        // Discover the base memory address where our Assembly is loaded
        var entryModule = Assembly.ManifestModule;
        var hMod = Marshal.GetHINSTANCE(entryModule);
        if (hMod == IntPtr.Zero - 1) throw new Exception("Failed to get HINSTANCE.");

        // Read the linker timestamp
        var offset = Marshal.ReadInt32(hMod, PE_HEADER_OFFSET);
        var secondsSince1970 = Marshal.ReadInt32(hMod, offset + LINKER_TIMESTAMP_OFFSET);

        // Convert the timestamp to a DateTime
        var Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var linkTimeUtc = Epoch.AddSeconds(secondsSince1970);
        var dt = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tzi ?? TimeZoneInfo.Local);
        return dt;
    }
}
10
tcostin

ここで説明されていないオプションは、AssemblyInfo.csに独自のデータを挿入することです。「AssemblyInformationalVersion」フィールドは適切だと思われます-ビルドステップと同様のことを行っていたプロジェクトがいくつかあります(ただし、動作するので、実際に私たちが持っているものを再現したくない)。

Codeprojectのテーマに関する記事があります。 http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

8
Murph

.NET Coreプロジェクトの場合、Postlagerkarteの回答を採用して、アセンブリの著作権フィールドをビルド日で更新しました。

Csprojを直接編集する

以下は、csprojの最初のPropertyGroupに直接追加できます。

<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>

代替:Visual Studioプロジェクトのプロパティ

または、Visual Studioのプロジェクトプロパティの[パッケージ]セクションにある[著作権]フィールドに内部式を直接貼り付けます。

Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))

Visual Studioは式を評価し、ウィンドウに現在の値を表示するため、これは少し混乱する可能性がありますが、プロジェクトファイルを舞台裏で適切に更新します。

Directory.Build.propsによるソリューション全体

上記の<Copyright>要素をソリューションルートのDirectory.Build.propsファイルに挿入し、各プロジェクトが独自のCopyright値を提供しないと仮定して、ディレクトリ内のすべてのプロジェクトに自動的に適用することができます。

<Project>
 <PropertyGroup>
   <Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
 </PropertyGroup>
</Project>

Directory.Build.props: ビルドのカスタマイズ

出力

式の例では、次のような著作権が付与されます。

Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)

検索

Windowsのファイルプロパティから著作権情報を表示したり、実行時に取得したりできます。

var version = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);

Console.WriteLine(version.LegalCopyright);
8
Travis Troyer

ここにはたくさんの素晴らしい答えがありますが、シンプルさ、パフォーマンス(リソース関連のソリューションと比較して)クロスプラットフォーム(Net Coreでも動作します)、およびサードパーティのツールの回避のために、自分で追加できると思います。このmsbuildターゲットをcsprojに追加するだけです。

<Target Name="Date" BeforeTargets="CoreCompile">
    <WriteLinesToFile File="$(IntermediateOutputPath)gen.cs" Lines="static partial class Builtin { public static long CompileTime = $([System.DateTime]::UtcNow.Ticks) %3B }" Overwrite="true" />
    <ItemGroup>
        <Compile Include="$(IntermediateOutputPath)gen.cs" />
    </ItemGroup>
</Target>

そして、必要に応じてBuiltin.CompileTimeまたはnew DateTime(Builtin.CompileTime, DateTimeKind.Utc)が必要になりました。

ReSharperは好きではありません。彼を無視するか、プロジェクトに部分クラスを追加することもできますが、それでも動作します。

7
Dmitry Gusarov

2018年には、上記のソリューションの一部が動作しなくなるか、.NET Coreで動作しなくなります。

私は次のアプローチを使用します。これはシンプルで、.NET Core 2.0プロジェクトで機能します。

PropertyGroup内の.csprojに次を追加します。

    <Today>$([System.DateTime]::Now)</Today>

これは、ビルド前コマンドでアクセスできる PropertyFunction を定義します。

ビルド前は次のようになります

echo $(today) > $(ProjectDir)BuildTimeStamp.txt

BuildTimeStamp.txtのプロパティをEmbeddedリソースに設定します。

今、あなたはこのようなタイムスタンプを読むことができます

public static class BuildTimeStamp
    {
        public static string GetTimestamp()
        {
            var Assembly = Assembly.GetEntryAssembly(); 

            var stream = Assembly.GetManifestResourceStream("NamespaceGoesHere.BuildTimeStamp.txt");

            using (var reader = new StreamReader(stream))
            {
                return reader.ReadToEnd();
            }
        }
    }
6
Postlagerkarte

私はただ:

File.GetCreationTime(GetType().Assembly.Location)
4
Rui Santos

あらゆるプラットフォーム(iOS、Android、Windows)のNETStandardプロジェクトで動作するユニバーサルソリューションが必要でした。これを実現するために、PowerShellスクリプトを介してCSファイルを自動的に生成することにしました。 PowerShellスクリプトは次のとおりです。

param($outputFile="BuildDate.cs")

$buildDate = Get-Date -date (Get-Date).ToUniversalTime() -Format o
$class = 
"using System;
using System.Globalization;

namespace MyNamespace
{
    public static class BuildDate
    {
        public const string BuildDateString = `"$buildDate`";
        public static readonly DateTime BuildDateUtc = DateTime.Parse(BuildDateString, null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
    }
}"

Set-Content -Path $outputFile -Value $class

PowerScriptファイルをGenBuildDate.ps1として保存し、プロジェクトに追加します。最後に、ビルド前イベントに次の行を追加します。

powershell -File $(ProjectDir)GenBuildDate.ps1 -outputFile $(ProjectDir)BuildDate.cs

BuildDate.csがプロジェクトに含まれていることを確認してください。どのOSでもチャンピオンのように動作します!

4
David Tosi

このプロジェクトを使用できます: https://github.com/dwcullop/BuildInfo

T4を活用して、ビルド日付のタイムスタンプを自動化します。あなたがそのようなことをしているなら、現在チェックアウトされているブランチのGitハッシュをあなたに与えるものを含むいくつかのバージョン(異なるブランチ)があります。

開示:モジュールを作成しました。

3
Darrin Cullop

Jhonからの「New Way」の回答に関する小さな更新。

ASP.NET/MVCを使用する場合は、CodeBase文字列を使用する代わりにパスを構築する必要があります

    var codeBase = Assembly.GetName().CodeBase;
    UriBuilder uri = new UriBuilder(codeBase);
    string path = Uri.UnescapeDataString(uri.Path);

別のPCLに適したアプローチは、MSBuildインラインタスクを使用して、アプリのプロパティによって返される文字列にビルド時間を代入することです。 Xamarin.Forms、Xamarin.Android、およびXamarin.iOSプロジェクトを持つアプリでこのアプローチを使用しています。

編集:

すべてのロジックをSetBuildDate.targetsファイルに移動し、単純な文字列置換の代わりにRegexを使用して、「リセット」なしで各ビルドでファイルを変更できるようにすることで簡略化しました。

MSBuildインラインタスク定義(この例では、Xamarin.FormsプロジェクトにローカルのSetBuildDate.targetsファイルに保存されます):

<Project xmlns='http://schemas.Microsoft.com/developer/msbuild/2003' ToolsVersion="12.0">

  <UsingTask TaskName="SetBuildDate" TaskFactory="CodeTaskFactory" 
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
    <ParameterGroup>
      <FilePath ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs"><![CDATA[

        DateTime now = DateTime.UtcNow;
        string buildDate = now.ToString("F");
        string replacement = string.Format("BuildDate => \"{0}\"", buildDate);
        string pattern = @"BuildDate => ""([^""]*)""";
        string content = File.ReadAllText(FilePath);
        System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(pattern);
        content = rgx.Replace(content, replacement);
        File.WriteAllText(FilePath, content);
        File.SetLastWriteTimeUtc(FilePath, now);

   ]]></Code>
    </Task>
  </UsingTask>

</Project>

ターゲットBeforeBuildのXamarin.Forms csprojファイルで上記のインラインタスクを呼び出します。

  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.  -->
  <Import Project="SetBuildDate.targets" />
  <Target Name="BeforeBuild">
    <SetBuildDate FilePath="$(MSBuildProjectDirectory)\BuildMetadata.cs" />
  </Target>

FilePathプロパティは、Xamarin.FormsプロジェクトのBuildMetadata.csファイルに設定されます。このファイルには、ビルド時間が置き換えられる文字列プロパティBuildDateを持つ単純なクラスが含まれます。

public class BuildMetadata
{
    public static string BuildDate => "This can be any arbitrary string";
}

このファイルBuildMetadata.csをプロジェクトに追加します。ビルドごとに変更されますが、ビルドの繰り返し(置換の繰り返し)が可能な方法で行われるため、必要に応じてソース管理に含めるか、省略することができます。

2
Mark Larter

よくわかりませんが、おそらく Build Incrementer が役立ちます。

2
Bobby

プロジェクトのビルド後イベントを使用して、現在の日時のテキストファイルをターゲットディレクトリに書き込むことができます。その後、実行時に値を読み取ることができます。それは少しハッキーですが、動作するはずです。

2
MikeWyatt

私はアブドゥラヒムの提案を使用しました。しかし、奇妙な時間形式を与えているようで、ビルド日付の一部としてその日の略語も追加しました。例:日12/24/2017 13:21:05.43。日付だけが必要だったため、部分文字列を使用して残りを削除する必要がありました。

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"をビルド前イベントに追加した後、次のことを行いました。

string strBuildDate = YourNamespace.Properties.Resources.BuildDate;
string strTrimBuildDate = strBuildDate.Substring(4).Remove(10);

ここでの良いニュースは、それが機能したことです。

1
DemarcPoint

ビルドプロセスの追加ステップを起動して、日付スタンプをファイルに書き込み、表示することができます。

プロジェクトのプロパティタブで、ビルドイベントタブを確認します。ビルド前またはビルド後のコマンドを実行するオプションがあります。

1

これがWindowsアプリの場合、アプリケーションの実行可能パスを使用できます:new System.IO.FileInfo(Application.ExecutablePath).LastWriteTime.ToString( "yyyy.MM.dd")

0
John Clark