Visualizzazione della data di costruzione

Al momento ho un’app che mostra il numero di build nella sua finestra del titolo. Va bene, ma non significa nulla per la maggior parte degli utenti, che vogliono sapere se hanno l’ultima build – tendono a riferirsi ad essa come “lo scorso giovedì” piuttosto che build 1.0.8.4321.

Il piano è di mettere la data di costruzione invece – Quindi “App costruita il 21/10/2009” per esempio.

Sto faticando a trovare un modo programmatico per estrarre la data di costruzione come una stringa di testo da utilizzare in questo modo.

Per il numero di build, ho usato:

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

dopo aver definito come sono arrivati ​​quelli.

Mi piacerebbe qualcosa del genere per la data di compilazione (e ora, per i punti bonus).

Puntatori qui molto apprezzati (scusate il gioco di parole se appropriato) o soluzioni più ordinate …

Jeff Atwood ha avuto alcune cose da dire su questo problema in Determining Build Date nel modo più difficile .

Il metodo più affidabile risulta essere il recupero del timestamp del linker dall’intestazione PE incorporata nel file eseguibile – un codice C # (di Joe Spivey) per quello dai commenti all’articolo di 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; } 

Esempio di utilizzo:

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

AGGIORNAMENTO: il metodo stava funzionando per .Net Core 1.0, ma ha smesso di funzionare dopo la versione .Net Core 1.1 (fornisce gli anni casuali nell’intervallo 1900-2020)

Il nuovo modo

Ho cambiato idea su questo, e attualmente uso questo trucco per ottenere la data di costruzione corretta.

 #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 

Alla vecchia maniera

Bene, come si generano i numeri di build? Visual Studio (o il compilatore C #) fornisce effettivamente numeri di compilazione e revisione automatici se si modifica l’attributo AssemblyVersion ad esempio 1.0.*

Ciò che accadrà è che la build sarà uguale al numero di giorni dall’ora locale del 1 ° gennaio 2000 e che la revisione sarà uguale al numero di secondi dall’ora locale di mezzanotte, divisa per 2.

vedere il contenuto della Community, i numeri di Build e Revision automatici

es. 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) 

Aggiungi qui sotto alla riga di comando pre-build dell’evento:

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

Aggiungi questo file come risorsa, ora hai la stringa ‘BuildDate’ nelle tue risorse.

Per creare risorse, vedere Come creare e utilizzare le risorse in .NET .

Aggiungi qui sotto alla riga di comando pre-build dell’evento:

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

Aggiungi questo file come risorsa, ora hai la stringa ‘BuildDate’ nelle tue risorse.

Dopo aver inserito il file nella Risorsa (come file di testo pubblico), l’accesso tramite

 string strCompTime = Properties.Resources.BuildDate; 

Per creare risorse, vedere Come creare e utilizzare le risorse in .NET .

Sono solo un novellino di C #, quindi forse la mia risposta è sciocca – visualizzo la data di costruzione dalla data in cui il file eseguibile è stato scritto l’ultima volta:

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

Ho provato a utilizzare il metodo File.GetCreationTime ma ho ottenuto risultati strani: la data del comando era 2012-05-29, ma la data di Window Explorer è stata del 2012-05-23. Dopo aver cercato questa discrepanza ho scoperto che il file è stato probabilmente creato il 2012-05-23 (come mostrato da Windows Explorer), ma copiato nella cartella corrente il 2012-05-29 (come mostrato dal comando File.GetCreationTime) – così per sicurezza, sto usando il comando File.GetLastWriteTime.

Zalek

Un approccio di cui sono stupito che nessuno ha ancora menzionato è quello di utilizzare i modelli di testo T4 per la generazione del codice.

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

Professionisti:

  • Locale indipendente
  • Permette molto più del tempo di compilazione

Contro:

  • Applicabile solo alle biblioteche in cui controlli la fonte
  • È necessario configurare il progetto (e creare il server, se non lo preleva) per eseguire il modello in una fase di pre-costruzione . (Vedi anche T4 senza VS ).

L’opzione non discussa qui è quella di inserire i propri dati in AssemblyInfo.cs, il campo “AssemblyInformationalVersion” sembra appropriato – abbiamo un paio di progetti in cui stavamo facendo qualcosa di simile come step di costruzione (tuttavia non sono del tutto soddisfatto modo che funziona così non voglio davvero riprodurre ciò che abbiamo).

C’è un articolo sull’argomento su codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

Per chiunque abbia bisogno di ottenere il tempo di compilazione in Windows 8 / Windows Phone 8:

  public static async Task 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); } } } 

Per chiunque abbia bisogno di ottenere il tempo di compilazione in Windows Phone 7:

  public static async Task 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(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: In tutti i casi si sta eseguendo in una sandbox, quindi sarà ansible solo ottenere il tempo di compilazione degli assembly che si distribuiscono con l’app. (cioè questo non funzionerà su nulla nel GAC).

Il metodo sopra descritto può essere ottimizzato per gli assiemi già caricati all’interno del processo utilizzando l’immagine del file in memoria (anziché rileggerla dallo spazio di archiviazione):

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

Per quanto riguarda la tecnica di estrazione delle informazioni su data / versione di build dai byte di un’intestazione PE dell’assieme, Microsoft ha modificato i parametri di generazione predefiniti a partire da Visual Studio 15.4. Il nuovo predefinito include la compilazione deterministica, che rende un timestamp valido e incrementa automaticamente i numeri di versione una cosa del passato. Il campo timestamp è ancora presente ma viene riempito con un valore permanente che è un hash di qualcosa o altro, ma non un’indicazione del tempo di costruzione.

http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html Alcuni dettagli qui

Per coloro che danno priorità a un timestamp utile rispetto alla compilazione deterministica, esiste un modo per ignorare il nuovo valore predefinito. Puoi includere un tag nel file .csproj dell’assembly di interesse come segue:

   ... false  

Aggiornamento: approvo la soluzione del modello di testo T4 descritta in un’altra risposta qui. L’ho usato per risolvere il mio problema in modo pulito senza perdere il vantaggio della compilazione deterministica. Un consiglio è che Visual Studio esegue solo il compilatore T4 quando il file .tt viene salvato, non al momento della compilazione. Questo può essere imbarazzante se si esclude il risultato .cs dal controllo del codice sorgente (poiché si prevede che venga generato) e un altro sviluppatore verifichi il codice. Senza salvare nuovamente, non avranno il file .cs. C’è un pacchetto su nuget (penso chiamato AutoT4) che rende la compilazione T4 parte di ogni build. Non ho ancora affrontato la soluzione a questo problema durante l’implementazione della produzione, ma mi aspetto che qualcosa di simile renda corretto.

È ansible utilizzare un evento post-build del progetto per scrivere un file di testo nella directory di destinazione con il datetime corrente. Potresti quindi leggere il valore in fase di esecuzione. È un po ‘hacky, ma dovrebbe funzionare.

Non sono sicuro, ma forse l’ Build Incrementer aiuta.

Un approccio diverso, basato su PCL, sarebbe quello di utilizzare un’attività incorporata in MSBuild per sostituire il tempo di compilazione in una stringa restituita da una proprietà nell’app. Stiamo utilizzando con successo questo approccio in un’app con progetti Xamarin.Forms, Xamarin.Android e Xamarin.iOS.

MODIFICARE:

Semplificato spostando tutta la logica nel file SetBuildDate.targets e utilizzando Regex invece di sostituire una stringa semplice in modo che il file possa essere modificato da ogni build senza un “reset”.

La definizione dell’attività inline di MSBuild (salvata in un file SetBuildDate.targets locale per il progetto Xamarin.Forms per questo esempio):

        \"{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); ]]>    

Richiamo dell’attività inline sopra nel file csproj Xamarin.Form in target BeforeBuild:

       

La proprietà FilePath è impostata su un file BuildMetadata.cs nel progetto Xamarin.Forms che contiene una class semplice con una proprietà stringa BuildDate , in cui verrà sostituito il tempo di costruzione:

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

Aggiungi questo file BuildMetadata.cs al progetto. Verrà modificato da ogni build, ma in un modo che consente ripetizioni di build (sostituzioni ripetute), quindi è ansible includerlo o ometterlo nel controllo del codice sorgente come desiderato.

Nel 2018 alcune delle soluzioni di cui sopra non funzionano più o non funzionano con .NET Core.

Io uso il seguente approccio che è semplice e funziona per il mio progetto .NET Core 2.0.

Aggiungi quanto segue al tuo .csproj all’interno del PropertyGroup:

  $([System.DateTime]::Now) 

Questo definisce una proprietà che è ansible accedere nel comando pre compilazione.

La tua pre-build sembra così

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

Impostare la proprietà di BuildTimeStamp.txt su Embedded resource.

Ora puoi leggere il timestamp come questo

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

Avevo bisogno di una soluzione universale che funzionasse con un progetto NETStandard su qualsiasi piattaforma (iOS, Android e Windows). Per riuscirci, ho deciso di generare automaticamente un file CS tramite uno script PowerShell. Ecco lo script di 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 

Salvare il file PowerScript come GenBuildDate.ps1 e aggiungerlo al progetto. Infine, aggiungi la seguente riga al tuo evento Pre-Build:

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

Assicurati che BuildDate.cs sia incluso nel tuo progetto. Funziona come un campione su qualsiasi sistema operativo!

Per i progetti .NET Core, ho adattato la risposta di Postlagerkarte per aggiornare il campo Copyright dell’assembly con la data di costruzione.

Modifica direttamente csproj

Quanto segue può essere aggiunto direttamente al primo PropertyGroup in csproj:

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

Alternativa: Proprietà del progetto Visual Studio

Oppure incollare l’espressione interna direttamente nel campo Copyright nella sezione Pacchetto delle proprietà del progetto in Visual Studio:

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

Questo può essere un po ‘di confusione, perché Visual Studio valuterà l’espressione e visualizzerà il valore corrente nella finestra, ma aggiornerà anche il file di progetto in modo appropriato dietro le quinte.

Soluzione a livello di directory tramite Directory.Build.props

È ansible plopare l’elemento in un file Directory.Build.props nella root della soluzione e applicarlo automaticamente a tutti i progetti all’interno della directory, presupponendo che ciascun progetto non fornisca il proprio valore di Copyright.

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

Directory.Build.props: personalizza la tua build

Produzione

L’espressione di esempio ti darà un copyright come questo:

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

Recupero

È ansible visualizzare le informazioni sul copyright dalle proprietà del file in Windows o acquisirlo in fase di runtime:

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

Un sacco di grandi risposte qui, ma mi sento come se potessi aggiungere il mio a causa della semplicità, delle prestazioni (rispetto alle soluzioni legate alle risorse) della piattaforma (funziona anche con Net Core) ed evitando qualsiasi strumento di terze parti. Basta aggiungere questo target msbuild a csproj.

       

e ora hai Builtin.CompileTime o new DateTime(Builtin.CompileTime, DateTimeKind.Utc) se ti serve in quel modo.

ReSharper non piacerà. Puoi ignorarlo o aggiungere una class parziale al progetto, ma funziona comunque.

È ansible avviare un ulteriore passaggio nel processo di generazione che scrive un timbro data su un file che può essere visualizzato.

Nella scheda delle proprietà del progetto, guarda la scheda degli eventi di compilazione. C’è un’opzione per eseguire un comando pre o post build.

Ho usato il suggerimento di Abdurrahim. Tuttavia, sembrava dare un formato di tempo strano e anche aggiunto l’abbreviazione per il giorno come parte della data di costruzione; esempio: dom 12/24/2017 13: 21: 05.43. Avevo solo bisogno della data, quindi ho dovuto eliminare il resto utilizzando la sottostringa.

Dopo aver aggiunto l’ echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt" pre-build, ho appena fatto quanto segue:

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

La buona notizia qui è che ha funzionato.

Un piccolo aggiornamento sulla risposta “New Way” di Jhon.

È necessario creare il percorso anziché utilizzare la stringa CodeBase quando si lavora con ASP.NET/MVC

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

Se si tratta di un’app di Windows, è sufficiente utilizzare il percorso dell’eseguibile dell’applicazione: nuovo System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString (“yyyy.MM.dd”)

Puoi utilizzare questo progetto: https://github.com/dwcullop/BuildInfo

Sfrutta T4 per automatizzare il timestamp della data di costruzione. Ci sono diverse versioni (rami diversi) tra cui quella che ti dà l’hash di Git del ramo attualmente estratto, se ti piace questo genere di cose.

Divulgazione: ho scritto il modulo.