Как я могу автоматизировать получение даты сборки в константу, видимую моему коду?

Я хотел бы определить в своем коде константу, удерживающую дату, на которой был построен исполняемый файл. Естественно, я хотел бы автоматизировать этот процесс.

Я знаю, что могу написать сценарий предварительной сборки, используя, например, Perl, чтобы написать .inc файл, содержащий дату. Я бы предпочел более легкое решение, используя, возможно, переменные среды или переменные сборки. Предоставляет ли msbuild какие-либо переменные, которые помогут? Кто-нибудь знает более аккуратное решение проблема?

7 ответов


вы можете прочитать временную метку компоновщика из заголовок PE исполняемого файла:

uses
  ImageHlp;

function LinkerTimeStamp(const FileName: string): TDateTime; overload;
var
  LI: TLoadedImage;
begin
  Win32Check(MapAndLoad(PChar(FileName), nil, @LI, False, True));
  Result := LI.FileHeader.FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta;
  UnMapAndLoad(@LI);
end;

для загруженного изображения текущего модуля, следующий, кажется, работает:

function LinkerTimestamp: TDateTime; overload;
begin
  Result := PImageNtHeaders(HInstance + Cardinal(PImageDosHeader(HInstance)^._lfanew))^.FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta;
end;

более ранние версии Delphi не обновили его правильно, но он был исправлен вокруг Delphi 2010 или около того. Для более ранних версий, я использовал IDE плагин для автоматического обновления после успешной компиляции.

Примечание: значение хранится в формате UTC, поэтому для отображения вам может потребоваться преобразовать его в соответствующий часовой пояс.


Если вы используете/JCL затем jclPEImage.PeReadLinkerTimeStamp() делает то, что вам нужно. Старые версии Delphi не устанавливали временную метку компоновщика, но новые версии AFAIK (т. е., похоже, работают нормально с D2010).


DDevExtensions имеет возможность включить дату и время компиляции в ресурс versioninfo. Думаю, мне не нужно показывать Вам, как извлечь это из программы...

обновление в отношении автоматизированной сборки: FinalBuilder имеет аналогичную опцию.


на самом деле TCompile не является Delphi unite. Вы можете найти его здесь, на EDN. И (просто копия страницы) :

Невизуальный компонент, который изменяет файл блока " проект.па." Этот файл имеет дату и время, проект был скомпилирован вместе с номером сборки.Удалите этот компонент в TForm, затем задайте путь к проекту. Это можно сделать, поместив курсор в поле свойства ProjectPath и нажав клавишу ENTER.

включить "Проект" в предложении USES; сохраните проект, затем выйдите Дельфи. При следующем открытии проекта в Delphi, файл блока "Проект.ПА" будет создан. Затем каждый раз, когда вы запускаете программу из внутри Delphi файл блока будет обновлен с текущей датой, время и номер сборки.


Я использую FinalBuilder для выполнения всех моих сборок, и с этим легко добавить утилиту, которая обновит любой исходный файл до компиляции для поиска и изменения определения переменной или константы. Я создаю номера версий, даты, все, что имеет смысл таким образом.


этот код работает в новых версиях Delphi, сочетает в себе получение времени и преобразование его в местное время.

function GetExeBuildTime: TDateTime;
var
  LI: TLoadedImage;
  {$IF CompilerVersion >= 26.0}
  m: TMarshaller;
  {$IFEND}
  timeStamp: Cardinal;
  utcTime: TDateTime;
begin
  {$IF CompilerVersion >= 26.0} //XE7 requires TMarshaller to convert to PAnsiChar
  Win32Check(MapAndLoad(PAnsiChar(m.AsAnsi(ParamStr(0)).ToPointer), nil, @LI, False, True));
  {$ELSE}
  Win32Check(MapAndLoad(PAnsiChar(AnsiString(ParamStr(0))), nil, @LI, False, True));
  {$IFEND}
  timeStamp := LI.FileHeader.FileHeader.TimeDateStamp;
  UnMapAndLoad(@LI);

  utcTime := UnixToDateTime(timeStamp);
  Result := TTimeZone.Local.ToLocalTime(utcTime);
end;

любая версия Delphi, просто пакет windows для создания .inc файл перед сборкой. IDE вообще не нужна.

вот скрипт:

REM CommandInterpreter: $(COMSPEC)
@echo off

setlocal enabledelayedexpansion  
setlocal
rem determine project top level directory from command file name
set PRJDIR=%~dp0
cd %PRJDIR%

set BUILD_DATE_FILE=%PRJDIR%Source\BuildDate.inc

call :GetGurrentDateTime&set BUILD_YEAR=!current_year!&set BUILD_MONTH=!current_month!&set BUILD_DAY=!current_day!&set BUILD_TIME=!current_time!

echo const BUILD_YEAR = %BUILD_YEAR%;> "%BUILD_DATE_FILE%"
:: 3 letter name Apr for April
echo const BUILD_MONTH = '%BUILD_MONTH%';>> "%BUILD_DATE_FILE%"
echo const BUILD_DAY = %BUILD_DAY%;>> "%BUILD_DATE_FILE%"
echo const BUILD_TIME = '%BUILD_TIME%';>> "%BUILD_DATE_FILE%"

goto :EOF
echo Done
exit /b 0

:GetGurrentDateTime
rem GET CURRENT DATE
echo.>"%TEMP%\~.ddf"
makecab /D RptFileName="%TEMP%\~.rpt" /D InfFileName="%TEMP%\~.inf" -f "%TEMP%\~.ddf">nul
for /f "tokens=4,5,6,7" %%a in ('type "%TEMP%\~.rpt"') do (
    if not defined current_date ( 
        set "current_date=%%d-%%a-%%b"
        set "current_time=%%c"   
    set "current_year=%%d"
    set "current_month=%%a"
    set "current_day=%%b"
    )
)

в исходном коде просто включите BuildDate.inc файл и использовать consts BUILD_*.