Hiển thị ngày xây dựng


260

Tôi hiện có một ứng dụng hiển thị số bản dựng trong cửa sổ tiêu đề của nó. Điều đó tốt và tốt ngoại trừ điều đó có nghĩa là không có gì với hầu hết người dùng, những người muốn biết liệu họ có bản dựng mới nhất hay không - họ có xu hướng gọi nó là "thứ Năm tuần trước" thay vì bản 1.0.8.4321.

Kế hoạch là đặt ngày xây dựng ở đó thay vào đó - ví dụ: "Ứng dụng được xây dựng vào ngày 21/10/2009".

Tôi đang vật lộn để tìm một cách lập trình để kéo ngày xây dựng thành một chuỗi văn bản để sử dụng như thế này.

Đối với số bản dựng, tôi đã sử dụng:

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

sau khi xác định làm thế nào những người đi lên.

Tôi muốn một cái gì đó như thế cho ngày biên dịch (và thời gian, cho điểm thưởng).

Con trỏ ở đây được đánh giá cao (xin lỗi chơi chữ nếu thích hợp), hoặc giải pháp gọn gàng hơn ...


2
Tôi đã thử các cách được cung cấp để có được dữ liệu xây dựng của các cụm hoạt động trong các tình huống đơn giản nhưng nếu hai cụm được hợp nhất với nhau thì tôi không có thời gian xây dựng chính xác, đó là một giờ trong tương lai .. có gợi ý nào không?

Câu trả lời:


356

Jeff Atwood đã có một vài điều để nói về vấn đề này trong Xác định ngày xây dựng một cách khó khăn .

Phương pháp đáng tin cậy nhất hóa ra là lấy dấu thời gian của trình liên kết từ tiêu đề PE được nhúng trong tệp thực thi - một số mã C # (của Joe Spivey) cho điều đó từ các bình luận cho bài viết của 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;
}

Ví dụ sử dụng:

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

CẬP NHẬT: Phương thức này hoạt động cho .Net Core 1.0, nhưng đã ngừng hoạt động sau khi phát hành .Net Core 1.1 (cho các năm ngẫu nhiên trong phạm vi 1900-2020)


8
Tôi đã thay đổi giọng điệu về điều này một chút, tôi vẫn rất cẩn thận khi đào sâu vào tiêu đề PE thông minh. Nhưng theo như tôi có thể nói, công cụ PE này đáng tin cậy hơn nhiều so với việc sử dụng các số phiên bản, ngoài ra tôi sẽ không chỉ định số phiên bản tách biệt từ ngày xây dựng.
John Leidegren

6
Tôi thích cái này và đang sử dụng nó, nhưng dòng thứ hai đến dòng cuối cùng .AddHours()khá là hackish và (tôi nghĩ) sẽ không tính đến DST. Nếu bạn muốn nó trong giờ địa phương, bạn nên sử dụng chất tẩy rửa dt.ToLocalTime();thay thế. Phần giữa cũng có thể được đơn giản hóa rất nhiều với một using()khối.
JLRishe

6
Vâng, điều này đã ngừng hoạt động đối với tôi với lõi .net (1940, 1960, v.v.)
eoleary

7
Mặc dù việc sử dụng tiêu đề PE có vẻ là một lựa chọn tốt hiện nay, nhưng đáng lưu ý rằng MS đang thử nghiệm các bản dựng xác định (sẽ khiến tiêu đề này trở nên vô dụng) và thậm chí có thể khiến nó trở thành mặc định trong các phiên bản trình biên dịch tương lai của C # (vì lý do chính đáng). Tốt đọc: blog.paranoidcoding.com/2016/04/05/... và đây của câu trả lời liên quan đến .NET Core (TLDR: "đó là do thiết kế"): developercommunity.visualstudio.com/content/problem/35873/...
Paweł Bulwan

13
Đối với những người tìm thấy điều này không còn hoạt động, vấn đề không phải là vấn đề .NET Core. Xem câu trả lời của tôi dưới đây về các mặc định tham số xây dựng mới bắt đầu với Visual Studio 15.4.
Tom

107

Thêm bên dưới vào dòng lệnh sự kiện xây dựng trước:

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

Thêm tệp này dưới dạng tài nguyên, bây giờ bạn có chuỗi 'BuildDate' trong tài nguyên của mình.

Để tạo tài nguyên, hãy xem Cách tạo và sử dụng tài nguyên trong .NET .


4
+1 từ tôi, đơn giản và hiệu quả. Tôi thậm chí đã quản lý để lấy giá trị từ tệp bằng một dòng mã như thế này: String buildDate = <MyClassL LibraryName>
.Properies.Resource.BuildDate

11
một tùy chọn khác là tạo một lớp: (phải đưa vào dự án sau lần đầu tiên bạn biên dịch nó) -> không gian tên echo My.app.namespace {lớp tĩnh công cộng Xây dựng {chuỗi tĩnh công khai Timestamp = "% DATE %% TIME%" .Sub chuỗi (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - -> sau đó có thể gọi nó bằng Build.Timestamp
FabianSilva

9
Đây là một giải pháp tuyệt vời. Vấn đề duy nhất là các biến dòng lệnh% date% và% time% được bản địa hóa, do đó đầu ra sẽ thay đổi tùy thuộc vào ngôn ngữ Windows của người dùng.
VS

2
+1, đây là một phương pháp tốt hơn so với đọc các tiêu đề PE - bởi vì có một số tình huống hoàn toàn không hoạt động (ví dụ: Ứng dụng Windows Phone)
Matt Whitfield

17
Tài giỏi. Bạn cũng có thể sử dụng powershell để có quyền kiểm soát chính xác hơn đối với định dạng, ví dụ: để có được một datetime UTC được định dạng là ISO8601: powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Tài nguyên ngoài '$ (ProjectDir) Tài nguyên \ BuildDate.txt' "
diễn ra vào

90

Cách

Như được chỉ ra bởi @ c00000fd trong các bình luận . Microsoft đang thay đổi điều này. Và trong khi nhiều người không sử dụng phiên bản mới nhất của trình biên dịch của họ, tôi nghi ngờ sự thay đổi này làm cho cách tiếp cận này không thể nghi ngờ là xấu. Và trong khi đó là một bài tập thú vị, tôi khuyên mọi người chỉ nên nhúng ngày xây dựng vào nhị phân của mình thông qua bất kỳ phương tiện nào khác cần thiết nếu điều quan trọng là theo dõi ngày xây dựng của chính nhị phân.

Điều này có thể được thực hiện với một số thế hệ mã tầm thường có lẽ là bước đầu tiên trong tập lệnh xây dựng của bạn. Điều đó, và thực tế là các công cụ ALM / Build / DevOps giúp ích rất nhiều cho việc này và nên được ưu tiên hơn bất cứ điều gì khác.

Tôi để phần còn lại của câu trả lời này ở đây chỉ cho mục đích lịch sử.

Cách mới

Tôi đã thay đổi suy nghĩ về điều này và hiện đang sử dụng thủ thuật này để có được ngày xây dựng chính xác.

#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

Cách cũ

Làm thế nào để bạn tạo số xây dựng? Visual Studio (hoặc trình biên dịch C #) thực sự cung cấp các số sửa đổi và xây dựng tự động nếu bạn thay đổi thuộc tính AssociationVersion thành vd1.0.*

Điều sẽ xảy ra là việc xây dựng sẽ bằng số ngày kể từ ngày 1 tháng 1 năm 2000 và để sửa đổi bằng với số giây kể từ nửa đêm theo giờ địa phương, chia cho 2.

xem Nội dung cộng đồng, số bản dựng và sửa đổi tự động

ví dụ: HộiInfo.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)

3
Tôi vừa thêm một giờ nếuTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog

2
Thật không may, tôi đã sử dụng phương pháp này mà không kiểm tra kỹ lưỡng và nó đã cắn chúng tôi trong sản xuất. Vấn đề là khi trình biên dịch JIT khởi động trong thông tin tiêu đề PE bị thay đổi. Do đó downvote. Bây giờ tôi sẽ thực hiện 'nghiên cứu' không cần thiết để giải thích lý do tại sao chúng tôi xem ngày cài đặt là ngày xây dựng.
Jason D

8
@JasonD Trong vũ trụ nào vấn đề của bạn bằng cách nào đó trở thành vấn đề của tôi? Làm thế nào để bạn biện minh cho một downvote đơn giản chỉ vì bạn gặp phải một vấn đề mà việc triển khai này không được xem xét. Bạn đã nhận được điều này miễn phí và bạn đã kiểm tra nó kém. Ngoài ra, điều gì khiến bạn tin rằng tiêu đề đang được trình biên dịch JIT viết lại? Bạn đang đọc thông tin này từ bộ nhớ quá trình hoặc từ tập tin?
John Leidegren

6
Tôi đã nhận thấy rằng nếu bạn đang chạy trong một ứng dụng web, thuộc tính .Codebase dường như là một URL (tệp: // c: /path/to/binary.dll). Điều này khiến cuộc gọi File.Exists không thành công. Sử dụng "assembly.Location" thay cho thuộc tính CodeBase đã giải quyết vấn đề cho tôi.
mdryden

2
@JohnLeidegren: Đừng dựa vào tiêu đề Windows PE cho điều đó. Vì Windows 10 và các bản dựng có thể tái tạo , IMAGE_FILE_HEADER::TimeDateStamptrường được đặt thành một số ngẫu nhiên và không còn là dấu ấn thời gian.
c00000fd

51

Thêm bên dưới vào dòng lệnh sự kiện xây dựng trước:

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

Thêm tệp này dưới dạng tài nguyên, bây giờ bạn có chuỗi 'BuildDate' trong tài nguyên của mình.

Sau khi chèn tệp vào Tài nguyên (dưới dạng tệp văn bản công khai), tôi đã truy cập tệp qua

string strCompTime = Properties.Resources.BuildDate;

Để tạo tài nguyên, hãy xem Cách tạo và sử dụng tài nguyên trong .NET .


1
@DavidGorsline - đánh dấu nhận xét là chính xác vì nó trích dẫn câu trả lời khác này . Tôi không đủ uy tín để phục hồi sự thay đổi của bạn, nếu không tôi đã tự mình làm điều đó.
Ái Hà Lee

1
@Wai Ha Lee - a) câu trả lời mà bạn trích dẫn không đưa ra mã để thực sự lấy ngày / thời gian biên dịch. b) tại thời điểm tôi không có đủ danh tiếng để thêm nhận xét cho câu trả lời đó (điều mà tôi sẽ làm), chỉ để đăng. vì vậy c) Tôi đã đăng câu trả lời đầy đủ để mọi người có thể nhận được tất cả các chi tiết trong một khu vực ..
brewmanz

Nếu bạn thấy UTE% thay vì ngày%%, đánh dấu vào đây: developercommunity.visualstudio.com/content/problem/237752/... Tóm lại, làm như sau: echo% 25date% 25% 25time% 25
Qodex

41

Một cách tiếp cận mà tôi không ngạc nhiên khi chưa có ai đề cập đến là sử dụng Mẫu văn bản T4 để tạo mã.

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

Ưu điểm:

  • Địa phương độc lập
  • Cho phép nhiều hơn chỉ là thời gian biên dịch

Nhược điểm:


1
Vì vậy, đây là câu trả lời tốt nhất. 324 điểm để đi trước khi nó trở thành câu trả lời được bình chọn hàng đầu :). Stackoverflow cần một cách để hiển thị người leo núi nhanh nhất.
pauldendulk

1
@pauldendulk, sẽ không giúp được gì nhiều, vì câu trả lời được đánh giá cao nhất và câu trả lời được chấp nhận gần như luôn luôn nhận được phiếu bầu nhanh nhất. Câu trả lời được chấp nhận cho câu hỏi này có + 60 / -2 kể từ khi tôi đăng câu trả lời này.
Peter Taylor

Tôi tin rằng bạn cần thêm .ToString () vào Ticks của mình (nếu không tôi sẽ gặp lỗi biên dịch). Điều đó nói rằng, tôi đang chạy vào một đường cong học tập dốc ở đây, bạn có thể chỉ cho bạn cách sử dụng cái này trong chương trình chính không?
Andy

@Andy, bạn nói đúng về ToString (). Cách sử dụng chỉ là Constants.CompilationTimestampUtc. Nếu VS không tạo tệp C # với lớp thì bạn cần tìm ra cách để làm điều đó, nhưng câu trả lời phụ thuộc vào (ít nhất là) phiên bản của VS và loại tệp csproj, vì vậy, đó là quá nhiều chi tiết cho bài viết này.
Peter Taylor

1
Trong trường hợp những người khác đang tự hỏi, đây là những gì nó cần để làm cho nó hoạt động trên VS 2017: Tôi đã phải tạo mẫu này Design Time T4 (Mất một lúc để tìm hiểu, tôi đã thêm một mẫu Tiền xử lý trước). Tôi cũng phải bao gồm hội đồng này: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 làm tài liệu tham khảo cho dự án. Cuối cùng, mẫu của tôi phải bao gồm "bằng cách sử dụng Hệ thống;" trước không gian tên, nếu không thì tham chiếu đến DateTime không thành công.
Andy

20

Liên quan đến kỹ thuật lấy thông tin ngày / phiên bản xây dựng từ các byte của tiêu đề PE lắp ráp, Microsoft đã thay đổi các tham số xây dựng mặc định bắt đầu bằng Visual Studio 15.4. Mặc định mới bao gồm biên dịch xác định, làm cho dấu thời gian hợp lệ và số phiên bản tăng tự động trở thành quá khứ. Trường dấu thời gian vẫn còn nhưng nó được điền với một giá trị vĩnh viễn là hàm băm của thứ gì đó hoặc thứ khác, nhưng không có bất kỳ dấu hiệu nào về thời gian xây dựng.

Một số thông tin chi tiết tại đây

Đối với những người ưu tiên dấu thời gian hữu ích so với biên dịch xác định, có một cách để ghi đè mặc định mới. Bạn có thể bao gồm một thẻ trong tệp .csproj của hội đồng quan tâm như sau:

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

Cập nhật: Tôi xác nhận giải pháp mẫu văn bản T4 được mô tả trong câu trả lời khác tại đây. Tôi đã sử dụng nó để giải quyết vấn đề của mình một cách sạch sẽ mà không làm mất lợi ích của việc biên dịch xác định. Một lưu ý về điều đó là Visual Studio chỉ chạy trình biên dịch T4 khi tệp .tt được lưu chứ không phải lúc xây dựng. Điều này có thể gây khó xử nếu bạn loại trừ kết quả .cs khỏi kiểm soát nguồn (vì bạn mong muốn nó được tạo) và một nhà phát triển khác kiểm tra mã. Nếu không lưu lại, họ sẽ không có tệp .cs. Có một gói trên nuget (tôi nghĩ gọi là AutoT4) làm cho trình biên dịch T4 trở thành một phần của mọi bản dựng. Tôi chưa đối mặt với giải pháp cho vấn đề này trong quá trình triển khai sản xuất, nhưng tôi hy vọng điều gì đó tương tự sẽ làm cho nó đúng.


Điều này đã giải quyết vấn đề của tôi trong một sln sử dụng câu trả lời cũ nhất.
pauldendulk

Sự thận trọng của bạn về T4 là hoàn toàn công bằng, nhưng lưu ý rằng nó đã có trong câu trả lời của tôi.
Peter Taylor

15

Tôi chỉ là C # newbie nên có thể câu trả lời của tôi nghe thật ngớ ngẩn - Tôi hiển thị ngày xây dựng kể từ ngày tệp thực thi được ghi lần cuối vào:

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

Tôi đã thử sử dụng phương thức File.GetCreationTime nhưng nhận được kết quả kỳ lạ: ngày từ lệnh là 2012-05-29, nhưng ngày từ Window Explorer hiển thị 2012-05-23. Sau khi tìm kiếm sự khác biệt này, tôi thấy rằng tệp có thể được tạo vào ngày 2012-05-23 (như được hiển thị bởi Windows Explorer), nhưng được sao chép vào thư mục hiện tại vào ngày 2012-05-29 (như được hiển thị bởi lệnh File.GetCreationTime) - vì vậy để an toàn, tôi đang sử dụng lệnh File.GetLastWriteTime.

Zalek


4
Tôi không chắc đây có phải là bằng chứng từ việc sao chép tệp thực thi trên các ổ đĩa / máy tính / mạng không.
Rabbi tàng hình

Đây là điều đầu tiên xuất hiện nhưng bạn biết nó không đáng tin cậy, có nhiều phần mềm được sử dụng để di chuyển các tệp qua mạng không cập nhật các thuộc tính sau khi tải xuống, tôi sẽ đi theo câu trả lời của @ Abdurrahim.
Mubashar

Tôi biết điều này đã cũ, nhưng tôi chỉ tìm thấy với một số mã tương tự mà quá trình INSTALL (ít nhất là khi sử dụng clickonce) cập nhật thời gian tập tin lắp ráp. Không hữu ích lắm. Không chắc chắn rằng nó sẽ áp dụng cho giải pháp này, mặc dù.
bobwki

Bạn có thể thực sự muốn LastWriteTime, vì điều đó phản ánh chính xác thời gian mà tập tin thực thi được cập nhật.
David R Tribble

Xin lỗi nhưng thời gian ghi tệp thực thi không phải là dấu hiệu đáng tin cậy về thời gian xây dựng. Dấu thời gian tập tin có thể được viết lại do tất cả các loại bên ngoài phạm vi ảnh hưởng của bạn.
Tom

15

Có rất nhiều câu trả lời tuyệt vời ở đây nhưng tôi cảm thấy mình có thể tự thêm vì đơn giản, hiệu năng (so với các giải pháp liên quan đến tài nguyên) (cũng hoạt động với Net Core) và tránh mọi công cụ của bên thứ 3. Chỉ cần thêm mục tiêu msbuild này vào 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>

và bây giờ bạn có Builtin.CompileTimehoặc new DateTime(Builtin.CompileTime, DateTimeKind.Utc)nếu bạn cần nó theo cách đó.

ReSharper sẽ không thích nó. Bạn có thể bỏ qua anh ta hoặc thêm một lớp một phần vào dự án nhưng dù sao nó cũng hoạt động.


Tôi có thể xây dựng với điều này và phát triển cục bộ (chạy trang web) trong ASP.NET Core 2.1 nhưng xuất bản triển khai web từ VS 2017 không thành công với lỗi "Tên 'Được xây dựng' không tồn tại trong ngữ cảnh hiện tại". BỔ SUNG: Nếu tôi đang truy cập Builtin.CompileTimetừ chế độ xem Dao cạo.
Jeremy Cook

Trong trường hợp này tôi nghĩ bạn chỉ cần BeforeTargets="RazorCoreCompile"nhưng chỉ trong khi điều này nằm trong cùng một dự án
Dmitry Gusarov

tuyệt, nhưng làm thế nào để chúng ta đề cập đến các đối tượng được tạo ra? Dường như với tôi câu trả lời là thiếu một phần quan trọng ...
Matteo

1
@Matteo, như đã đề cập trong câu trả lời, bạn có thể sử dụng "Buildin.CompileTime" hoặc "DateTime mới (Buildin.CompileTime, DateTimeKind.Utc)". Visual Studio IntelliSense có khả năng nhìn thấy điều này ngay lập tức. Một ReSharper cũ có thể phàn nàn về thời gian thiết kế, nhưng có vẻ như họ đã sửa lỗi này trong các phiên bản mới. clip2net.com/s/46rgaaO
Dmitry Gusarov

Tôi đã sử dụng phiên bản này vì vậy không cần mã bổ sung để có được ngày. Ngoài ra, chia sẻ lại không phàn nàn với phiên bản mới nhất của nó. <WriteLinesToFile File = "$ (MiddleOutputPath) BuildInfo.cs" Lines = "bằng cách sử dụng lớp tĩnh nội bộ System% 3B BuildInfo {static static DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B Date static Date => DateTime mới (DateBuiltTicks, DateTimeKind.Utc)% 3B} "Ghi đè =" đúng "/>
Softlion

13

Đối với các dự án .NET Core, tôi đã điều chỉnh câu trả lời của Postlagerkarte để cập nhật trường Bản quyền lắp ráp với ngày xây dựng.

Chỉnh sửa trực tiếp csproj

Sau đây có thể được thêm trực tiếp vào đầu tiên PropertyGrouptrong csproj:

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

Thay thế: Thuộc tính dự án Visual Studio

Hoặc dán biểu thức bên trong trực tiếp vào trường Bản quyền trong phần Gói của thuộc tính dự án trong Visual Studio:

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

Điều này có thể hơi khó hiểu, vì Visual Studio sẽ đánh giá biểu thức và hiển thị giá trị hiện tại trong cửa sổ, nhưng nó cũng sẽ cập nhật tệp dự án một cách thích hợp phía sau hậu trường.

Toàn bộ giải pháp thông qua Directory.Build.props

Bạn có thể đưa <Copyright>phần tử ở trên vào một Directory.Build.propstệp trong thư mục gốc của giải pháp và để nó tự động áp dụng cho tất cả các dự án trong thư mục, giả sử mỗi dự án không cung cấp giá trị Bản quyền riêng.

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

Directory.Build.props: Tùy chỉnh bản dựng của bạn

Đầu ra

Biểu thức ví dụ sẽ cung cấp cho bạn bản quyền như thế này:

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

Lấy lại

Bạn có thể xem thông tin bản quyền từ các thuộc tính tệp trong Windows hoặc lấy thông tin đó trong thời gian chạy:

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

Console.WriteLine(version.LegalCopyright);

11

Phương pháp trên có thể được điều chỉnh cho các tập hợp đã được tải trong quy trình bằng cách sử dụng hình ảnh của tệp trong bộ nhớ (trái ngược với việc đọc lại từ bộ lưu trữ):

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

Công cụ này hoạt động rất tốt, ngay cả đối với khung 4.7 Cách sử dụng: Utils.GetLinkerDateTime (Hội.GetExecutingAssugging (), null))
real_yggdrasil

Hoạt động tuyệt vời! Cảm ơn!
bobwki

10

Đối với bất kỳ ai cần có thời gian biên dịch trong 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);
            }
        }
    }

Đối với bất kỳ ai cần có thời gian biên dịch trong 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;
    }

LƯU Ý: Trong mọi trường hợp bạn đang chạy trong hộp cát, vì vậy bạn sẽ chỉ có thể nhận được thời gian biên dịch các cụm mà bạn triển khai với ứng dụng của mình. (tức là điều này sẽ không hoạt động trên bất cứ thứ gì trong GAC).


Đây là cách bạn có được hội trong WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
André Fiedler

Điều gì nếu bạn muốn chạy mã của bạn trên cả hai hệ thống? - là một trong những phương pháp áp dụng cho cả hai nền tảng?
bvdb

10

Năm 2018 một số giải pháp trên không hoạt động nữa hoặc không hoạt động với .NET Core.

Tôi sử dụng cách tiếp cận sau đây đơn giản và hiệu quả cho dự án .NET Core 2.0 của tôi.

Thêm phần sau vào .csproj của bạn trong Thuộc tính nhóm:

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

Điều này xác định một thuộc tính mà bạn có thể truy cập trong lệnh xây dựng trước.

Bản dựng trước của bạn trông như thế này

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

Đặt thuộc tính của BuildTimeStamp.txt thành tài nguyên được nhúng.

Bây giờ bạn có thể đọc dấu thời gian như thế này

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

Chỉ cần tạo BuildTimeStamp.txt từ các sự kiện xây dựng trước bằng cách sử dụng các lệnh tập lệnh bó cũng hoạt động. Xin lưu ý rằng bạn đã mắc lỗi ở đó: bạn nên bao quanh mục tiêu của mình trong dấu ngoặc kép (ví dụ "$(ProjectDir)BuildTimeStamp.txt") hoặc nó sẽ bị hỏng khi có khoảng trắng trong tên thư mục.
Nyerguds

Có lẽ nó có ý nghĩa để sử dụng định dạng thời gian văn hóa bất biến. Như thế này: $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))thay vì$([System.DateTime]::Now)
Ivan Kochurkin

9

Tùy chọn không được thảo luận ở đây là chèn dữ liệu của riêng bạn vào hội nghịInIn.cs, trường "Hội đồng thông tin" có vẻ phù hợp - chúng tôi có một vài dự án mà chúng tôi đang làm một cái gì đó tương tự như một bước xây dựng (tuy nhiên tôi không hoàn toàn hài lòng với cách mà nó không thực sự muốn tái tạo những gì chúng ta đã có).

Có một bài viết về chủ đề trên codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx


6

Tôi cần một giải pháp phổ quát hoạt động với dự án NETSt Chuẩn trên mọi nền tảng (iOS, Android và Windows.) Để thực hiện điều này, tôi quyết định tự động tạo tệp CS thông qua tập lệnh PowerShell. Đây là tập lệnh 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

Lưu tệp PowerScript dưới dạng GenBuildDate.ps1 và thêm dự án của bạn. Cuối cùng, thêm dòng sau vào sự kiện Pre-Build của bạn:

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

Đảm bảo BuildDate.cs được bao gồm trong dự án của bạn. Hoạt động như một nhà vô địch trên bất kỳ hệ điều hành nào!


1
Bạn cũng có thể sử dụng số này để lấy số sửa đổi SVN bằng công cụ dòng lệnh svn. Tôi đã làm một cái gì đó tương tự như thế này với điều đó.
dùng169771

5

Tôi vừa làm:

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

1
Điều thú vị là, nếu chạy từ debug, ngày 'true' là GetLastAccessTime ()
Balint

4

Bạn có thể sử dụng dự án này: https://github.com/dwcullop/BuildInfo

Nó tận dụng T4 để tự động hóa dấu thời gian ngày xây dựng. Có một số phiên bản (các nhánh khác nhau) bao gồm một phiên bản cung cấp cho bạn Git Hash của nhánh hiện đang được kiểm tra, nếu bạn thuộc loại đó.

Tiết lộ: Tôi đã viết mô-đun.


3

Một cách tiếp cận khác, thân thiện với PCL sẽ là sử dụng tác vụ nội tuyến MSBuild để thay thế thời gian xây dựng thành một chuỗi được trả về bởi một thuộc tính trên ứng dụng. Chúng tôi đang sử dụng phương pháp này thành công trong một ứng dụng có các dự án Xamarin.Forms, Xamarin.Android và Xamarin.iOS.

BIÊN TẬP:

Đơn giản hóa bằng cách di chuyển tất cả logic vào SetBuildDate.targetstệp và sử dụng Regexthay thế chuỗi đơn giản để tệp có thể được sửa đổi bởi mỗi bản dựng mà không cần "đặt lại".

Định nghĩa tác vụ nội tuyến MSBuild (được lưu trong tệp SetBuildDate.target cục bộ cho dự án Xamarin.Forms cho ví dụ này):

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

Gọi tác vụ nội tuyến trên trong tệp Xproarin.Forms csproj trong đích 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>

Các FilePathtài sản được đặt thành một BuildMetadata.cstập tin trong dự án Xamarin.Forms có chứa một lớp đơn giản với một tài sản chuỗi BuildDate, vào đó thời gian xây dựng sẽ được thay thế:

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

Thêm tệp này BuildMetadata.csvào dự án. Nó sẽ được sửa đổi bởi mọi bản dựng, nhưng theo cách cho phép các bản dựng lặp lại (thay thế lặp đi lặp lại), do đó bạn có thể bao gồm hoặc bỏ qua nó trong kiểm soát nguồn như mong muốn.


2

Bạn có thể sử dụng một sự kiện hậu xây dựng dự án để viết một tệp văn bản vào thư mục đích của bạn với datetime hiện tại. Sau đó bạn có thể đọc giá trị tại thời gian chạy. Đó là một chút hack, nhưng nó sẽ làm việc.



2

Một bản cập nhật nhỏ về câu trả lời "Cách mới" từ Jhon.

Bạn cần xây dựng đường dẫn thay vì sử dụng chuỗi CodeBase khi làm việc với ASP.NET/MVC

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

1

Bạn có thể khởi chạy thêm một bước trong quy trình xây dựng ghi dấu ngày vào tệp có thể được hiển thị.

Trên tab thuộc tính dự án nhìn vào tab sự kiện xây dựng. Có một tùy chọn để thực hiện lệnh xây dựng trước hoặc sau.


1

Tôi đã sử dụng đề nghị của Abdurrahim. Tuy nhiên, nó dường như đưa ra một định dạng thời gian kỳ lạ và cũng đã thêm chữ viết tắt cho ngày như một phần của ngày xây dựng; ví dụ: CN 12/24/2017 13: 21: 05.43. Tôi chỉ cần ngày tháng vì vậy tôi phải loại bỏ phần còn lại bằng cách sử dụng chuỗi con.

Sau khi thêm echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"sự kiện xây dựng trước, tôi chỉ cần làm như sau:

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

Tin tốt ở đây là nó đã hoạt động.


Giải pháp rất đơn giản. Tôi thích nó. Và nếu định dạng là một điều phiền phức, có nhiều cách để có được định dạng tốt hơn từ dòng lệnh.
Nyerguds

0

Nếu đây là một ứng dụng windows, bạn chỉ có thể sử dụng đường dẫn thực thi của ứng dụng: System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ("yyyy.MM.dd")


2
Đã và trả lời bằng cách sử dụng này, và cũng không chính xác chống đạn.
crashmstr

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.