Pergunta

Actualmente tenho um aplicativo exibir o número de compilação na janela de título. Isso é muito bem, exceto que não significa nada para a maioria dos usuários, que querem saber se eles têm a compilação mais recente -. Eles tendem a se referir a ele como "última quinta-feira do" ao invés de construir 1.0.8.4321

O plano é colocar a data de construção lá em vez disso -. Assim, "App construída em 21/10/2009", por exemplo

Eu estou lutando para encontrar uma maneira programática para puxar a data de construção como uma cadeia de texto para uso como esta.

Para o número de compilação, eu usei:

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

após a definição de como os surgiu.

Eu gostaria de algo parecido para a data de compilação (e tempo, para pontos de bônus).

Os ponteiros aqui muito apreciada (desculpa pun se for o caso), ou soluções mais puro ...

Foi útil?

Solução

Jeff Atwood tinha algumas coisas a dizer sobre este assunto em Determinando Desenvolver Data da maneira mais difícil .

O método mais confiável acaba por ser a recuperação do timestamp vinculador do PE cabeçalho incorporado no arquivo executável - algum código C # (por Joe Spivey) para que a partir dos comentários ao artigo de Jeff:

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;
}

Exemplo de uso:

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

UPDATE: O método foi trabalhar para .Net núcleo 1.0, mas parou de funcionar depois .Net Núcleo 1.1 release (dá anos aleatórios em 1900-2020 intervalo)

Outras dicas

Adicionar abaixo para pré-build linha de comando evento:

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

Adicione esta arquivo como recurso, agora você tem string 'BuildDate' em seus recursos.

Para criar recursos, consulte Como criar e utilizar os recursos em. NET .

A maneira

Como apontado por @ c00000fd no . Microsoft está mudando isso. E enquanto muitas pessoas não usam a versão mais recente de seu compilador Eu suspeito que esta mudança faz com que esta abordagem indiscutivelmente ruim. E embora seja um exercício divertido Eu recomendaria que as pessoas simplesmente inserir uma data de construção em seu binário através de quaisquer outros meios necessários, se é importante para acompanhar a data de construção do próprio binário.

Isto pode ser feito com alguma geração de código trivial que provavelmente é o primeiro passo em seu script de construção já. Isso, eo fato de que ferramentas ALM / construção / DevOps ajudar muito com isso e deve ser preferido a qualquer outra coisa.

Deixo o resto desta resposta aqui para fins históricos somente.

A nova forma

Eu mudei minha mente sobre isso, e atualmente usar esse truque para obter a data de construção correto.

#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

A velha maneira

Bem, como você gerar números de compilação? Visual Studio (ou o compilador C #) realmente fornece números de compilação e revisão automática se você alterar o atributo AssemblyVersion de exemplo 1.0.*

O que vai acontecer é que é que a compilação será igual ao número de dias desde 01 de janeiro de 2000, hora local, e para a revisão a ser igual ao número de segundos desde a época meia-noite local, dividido por 2.

ver Conteúdo da Comunidade, números de compilação automática e revisão

por exemplo. 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)

Adicionar abaixo para pré-build linha de comando evento:

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

Adicione esta arquivo como recurso, agora você tem string 'BuildDate' em seus recursos.

Depois de inserir o arquivo no Resource (como arquivo de texto público), eu acessada lo através

string strCompTime = Properties.Resources.BuildDate;

Para criar recursos, consulte Como criar e utilizar os recursos em. NET .

Uma abordagem que eu estou espantado que ninguém mencionou ainda está para uso Modelos de texto T4 para geração de código.

<#@ 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); } }
    }
}

Pros:

  • Locale-independente
  • Permite muito mais do que apenas o tempo de compilação

Contras:

Com relação à técnica de puxar data de construção / info versão dos bytes de um cabeçalho PE montagem, Microsoft mudou os parâmetros de construção padrão, começando com Visual Studio 15.4. O novo padrão inclui compilação determinista, o que torna um timestamp válido e números de versão incrementados automaticamente uma coisa do passado. O campo timestamp ainda está presente, mas ele é preenchido com um valor permanente, que é um hash de uma coisa ou outra, mas não qualquer indicação do tempo de compilação.

Alguns antecedentes detalhada aqui

Para aqueles que priorizar um timestamp útil ao longo compilação determinista, há uma maneira de substituir o novo padrão. Você pode incluir uma tag no arquivo .csproj do conjunto de interesse da seguinte forma:

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

Update: Concordo com a solução de modelo de texto T4 descrito em outra resposta aqui. Usei-o para resolver o meu problema de forma limpa sem perder o benefício de compilação determinista. Um cuidado sobre ele é que Visual Studio é executado somente o compilador T4 quando o arquivo .tt é salvo, não em tempo de compilação. Isso pode ser difícil se você excluir os cs resultar de controle de origem (desde que você espera que ele seja gerado) e outro desenvolvedor verifica o código. Sem resaving, eles não terão o arquivo .cs. Há um pacote de NuGet (acho chamado AutoT4) que faz parte compilação T4 de cada compilação. Eu ainda não confrontou a solução para este durante a implantação de produção, mas espero que algo semelhante para torná-lo direito.

Eu sou apenas C # novato então talvez o meu som resposta idiota - eu mostrar a data de construção a partir da data do arquivo executável foi gravado pela última vez:

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());

Eu tentei usar o método File.GetCreationTime mas tem resultados estranhos: a data a partir do comando foi 2012-05-29, mas a data a partir do Explorador Janela mostrou 2012-05-23. Depois de procurar por esta discrepância descobri que o arquivo foi criado provavelmente em 2012-05-23 (como mostrado pelo Windows Explorer), mas copiados para a pasta atual em 2012-05-29 (como mostrado pelo comando File.GetCreationTime) - assim para estar no lado seguro eu estou usando o comando File.GetLastWriteTime.

Zalek

Para qualquer um que necessidades para obter o tempo de compilação no 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);
            }
        }
    }

Para qualquer um que necessidades para obter o tempo de compilação no 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;
    }

NOTA: Em todos os casos você está correndo em uma caixa de areia, então você só vai ser capaz de obter o tempo de compilação de conjuntos que você implanta com seu aplicativo. (Ou seja, isto não vai funcionar em qualquer coisa na GAC).

O método acima pode ser ajustado para os conjuntos já carregado no interior do processo usando a imagem do ficheiro em memória (em oposição a re lendo-lo a partir do armazenamento):

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;
    }
}

A opção não discutido aqui é inserir seus próprios dados em AssemblyInfo.cs, o campo "AssemblyInformationalVersion" parece adequado - temos um par de projectos onde estávamos fazendo algo semelhante como uma etapa de compilação (no entanto eu não sou inteiramente feliz com a maneira que funciona tão realmente não quiser reproduzir o que temos).

Há um artigo sobre o assunto na codeproject: http://www.codeproject.com /KB/dotnet/Customizing_csproj_files.aspx

Para projetos .NET núcleo, eu adaptei a resposta de Postlagerkarte para atualizar o campo Copyright Assembly com a data de criação.

Diretamente Editar csproj

O seguinte pode ser adicionado directamente ao primeiro PropertyGroup no csproj:

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

Alternativa: Visual Studio Propriedades do projeto

Ou cole a expressão interna diretamente no campo Direitos de autor na seção Pacote de propriedades do projeto no Visual Studio:

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

Isso pode ser um pouco confuso, porque o Visual Studio irá avaliar a expressão e exibir o valor atual na janela, mas que também irá atualizar o arquivo de projeto de forma adequada nos bastidores.

-Wide Solution

via Directory.Build.props

Você pode plop o elemento <Copyright> acima em um arquivo Directory.Build.props em sua solução de raiz, e tê-lo automaticamente aplicado a todos os projetos dentro do diretório, assumindo que cada projeto não fornecer o seu próprio valor de Copyright.

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

Directory.Build.props: Customize sua construção

saída

O exemplo a expressão lhe dará uma copyright como este:

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

Recuperação

Você pode visualizar as informações de copyright das propriedades do arquivo no Windows, ou agarrá-lo em tempo de execução:

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

Console.WriteLine(version.LegalCopyright);

Muitos dos grandes respostas aqui, mas eu sinto que posso acrescentar a minha própria por causa da simplicidade, desempenho (comparando com as soluções de recurso-relacionado) multiplataforma (trabalhos com núcleo líquido também) e evitar qualquer ferramenta de 3rd party. Basta adicionar esta meta msbuild ao 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>

e agora você tem Builtin.CompileTime ou new DateTime(Builtin.CompileTime, DateTimeKind.Utc) se você precisar dele dessa forma.

ReSharper não vai gostar. Você pode ignorá-lo ou adicionar uma classe parcial para o projeto também, mas ele funciona de qualquer maneira.

Em 2018 algumas das soluções acima não funcionam mais ou não fazer um trabalho com .NET Core.

Eu uso a seguinte abordagem que é simples e funciona para o meu projeto .NET Núcleo 2.0.

Adicione o seguinte ao seu .csproj dentro do PropertyGroup:

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

Isto define um PropertyFunction que você pode acessar em sua pré comando de compilação.

Seus pré-compilação olhares como este

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

Defina a propriedade do BuildTimeStamp.txt ao recurso incorporado.

Agora você pode ler o carimbo de hora como esta

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();
            }
        }
    }

Eu precisava de uma solução universal que trabalhou com um projeto netstandard em qualquer plataforma (iOS, Android e Windows.) Para conseguir isso, decidi gerar automaticamente um arquivo CS através de um script PowerShell. Aqui está o script 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

Salve o arquivo PowerScript como GenBuildDate.ps1 e adicione-o seu projeto. Finalmente, adicione a seguinte linha ao seu evento pré-compilação:

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

Certifique-se de BuildDate.cs está incluído em seu projeto. Funciona como um campeão em qualquer sistema operacional!

Eu só faço:

File.GetCreationTime(GetType().Assembly.Location)

Você pode usar este projeto: https://github.com/dwcullop/BuildInfo

Ele aproveita T4 para automatizar o timestamp data de construção. Existem várias versões (diferentes ramos), incluindo um que lhe dá o Git Hash do ramo em check-out, se você estiver em que tipo de coisa.

Divulgação:. Eu escrevi o módulo

Você pode usar um evento de projeto de pós-compilação para gravar um arquivo de texto para o seu diretório de destino com a data e hora atual. Você poderia, então, ler o valor em tempo de execução. É um pouco hacky, mas deve funcionar.

Eu não tenho certeza, mas talvez o Desenvolver incrementador ajuda.

Um diferente, abordagem PCL-friendly seria a utilização de uma tarefa em linha MSBuild para substituir o tempo de compilação em uma seqüência que é retornado por uma propriedade no aplicativo. Nós estamos usando esta abordagem com sucesso em um aplicativo que tem Xamarin.Forms, Xamarin.Android e projetos Xamarin.iOS.

EDIT:

simplificado, movendo toda a lógica no arquivo SetBuildDate.targets, e usando Regex em vez de simples substituição de string para que o arquivo pode ser modificado por cada construção sem um "reset".

A definição de tarefa em linha MSBuild (salvo em um SetBuildDate.targets arquivo local para o Xamarin.Forms projetar para este exemplo):

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

Invocando a tarefa em linha acima no arquivo os Xamarin.Forms csproj no alvo BeforeBuild:

  <!-- 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>

A propriedade FilePath está definido para um arquivo BuildMetadata.cs nas Xamarin.Forms projeto que contém uma classe simples com um BuildDate propriedade de cadeia, em que o tempo de compilação serão substituídos:

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

Adicione esta BuildMetadata.cs arquivo para projeto. Vai ser modificado por cada compilação, mas de uma forma que permite repetido constrói (substituições repetidas), para que você pode incluir ou omiti-lo no controle de origem, como desejado.

Uma pequena atualização sobre a resposta "New Way" de João.

Você precisa construir o caminho em vez de usar a corda CodeBase quando se trabalha com ASP.NET/MVC

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

Você poderia lançar um passo extra no processo de compilação que escreve um carimbo de data para um arquivo que pode ser exibido.

Na projetos propriedades olhar guia na guia eventos de compilação. Há uma opção para executar um comando pós construção pré ou.

Eu usei a sugestão de Abdurrahim. No entanto, parecia dar um formato tempo estranho e também a abreviatura para o dia como parte da data de construção; exemplo: Sun 2017/12/24 13: 21: 05,43. Eu só precisava apenas a data para que eu tinha para eliminar o resto usando substring.

Depois de adicionar o echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"to o evento pré-compilação, eu só fiz o seguinte:

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

A boa notícia aqui é que ele trabalhou.

Se este é um aplicativo do Windows, você pode simplesmente usar o caminho executável do aplicativo: nova System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ( "AAAA.MM.DD")

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top