【问题标题】:How to define application version in one place for multiple applications?如何在一个地方为多个应用程序定义应用程序版本?
【发布时间】:2012-07-31 17:04:41
【问题描述】:

我们有一个由众多应用程序组成的系统。所有应用程序的版本都会同时更改。目前,当我们发布新版本时,我们必须手动打开每个应用程序的项目选项,并一一更改版本。有没有办法在同一版本上编译所有应用程序,例如,将其保存在全局文件中,并在编译时读取该文件并将该版本分配给项目?我只是想消除太多步骤,因为我们计划更频繁地更改版本号。我只想在一个地方改变它。这可以做到吗?以及如何?

【问题讨论】:

  • 我使用自制工具为每个应用程序创建一个 .rc 文件,然后作为构建脚本的一部分编译为资源。换句话说,我不依赖任何内置设施。
  • @MarcusAdams 产品版本和文件版本。
  • 如果你使用像 FinalBuilder 这样的构建应用程序,你可以很容易地自动化这个过程。我在每个应用程序的主窗体顶部使用注释名称/值对,例如 { buildversion=3.0.1 },然后 Finalbuilder 在创建我的部署构建时读取名称值、递增和保存。

标签: delphi msbuild compilation version delphi-xe2


【解决方案1】:

您可以在纯文本文件中创建VERSIONINFO 资源(例如,Versioninfo.rc

1 VERSIONINFO
FILEVERSION 2,0,0,0
PRODUCTVERSION 2,0,0,0
FILEOS 0x4
FILETYPE 0x1
{
BLOCK "StringFileInfo"
{
    BLOCK "040904E4"
    {
        VALUE "CompanyName", "Your Company Name Here\0"
        VALUE "FileDescription", "Your File Description Here\0"
        VALUE "FileVersion", "2.0.0.0\0"
        VALUE "InternalName", "Your Internal Name\0"
        VALUE "LegalCopyright", "© Your Copyright Notice\0"
        VALUE "LegalTrademarks", "Your Trademark Notice\0"
        VALUE "OriginalFilename", "YourExeName\0"
        VALUE "ProductName", "Your Product Name\0"
        VALUE "ProductVersion", "2.0.0.0\0"
        VALUE "Comments", "No Comments\0"
    }
}

BLOCK "VarFileInfo"
{
    VALUE "Translation", 0x0409 0x04E4
}
}

注意:如图所示,每个项目的末尾都需要 C 样式的空终止符 (\0),以便资源编译器正确终止字符串。否则,当您使用资源管理器显示可执行文件的版本信息时,您可能会得到乱码或部分连接的值。

在您的项目源文件中添加一行:

{$R VersionInfo.res VersionInfo.rc}

我建议将通用版本信息资源放入版本控制系统中的外部引用中,然后您可以将其检出到每个项目的文件夹中并轻松更新。

做一个项目->构建,你的版本信息嵌入到 .exe 中。您可以使用 Windows 资源管理器并查看应用的属性进行验证。

在 CodeNewsFast 档案的 Embarcadero Delphi 论坛中有几篇文章(一篇由我撰写,一篇由 Jim Fleming 回复)。我的是 [here],在这里,我将逐步描述如何在您的项目中使用预构建事件来更新我在上面发布的资源脚本中的版本号。

Jim 发布了一些回复,但大约有十几个帖子有一个可执行文件的源代码,该可执行文件可以从适用于他的预构建事件中调用。 (有些事情我会做不同的事情,比如让 IDE 在命令行上传递项目名称和位置;如何做到这一点在分步文章中进行了描述。我还将处理版本解析和递增方式不同,但基本应用程序是一个很好的起始位置。)

Embarcadero 的小组目前已关闭,但我也能够从 CodeNewsFast 检索 Jim 的代码,并且可以在此处重现:

肯,

多亏了你,我得到了它的工作。

以防万一其他人想实施此解决方案,您将在下面找到必要的步骤和辅助程序。

吉姆·弗莱明

A) 在您的项目目录或任何地方创建您的版本信息资源文件,使用以下内容

内容和文件扩展名 .rc:

// Note the \000 !!!! Here and elsewhere below !!!! 
// C string terminator !!!
#define CONST_VERSION "4.1.1.1003\000" 

1 VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEOS 0x4
FILETYPE 0x1
{
BLOCK "StringFileInfo"
{

BLOCK "040904E4" // Will need changing if your language is not English and char-set not 1252 (multilingual).
{
VALUE "CompanyName", "Whatever\000"
VALUE "FileDescription", "Whatever\000"
VALUE "FileVersion", CONST_VERSION
VALUE "InternalName", "My Internal Name\000"
VALUE "LegalCopyright", "Copyright © whoever\000"
VALUE "LegalTrademarks", "\000"
VALUE "OriginalFileName", "If you wish\000"
VALUE "ProductName", "What pleases you\000"
VALUE "ProductVersion", CONST_VERSION
VALUE "Comments", "Anything you wish to add\000"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0409 0x04E4
}
}

B) 在某个文件夹中创建一个新项目,只有模块的代码应该类似于:

unit FormIncrementBuildNumber;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls, System.IOUtils, System.StrUtils;


type
  TIncrementBuildNumber = class(TForm)
    IncrementingBuildNumberLabel: TLabel;
    procedure FormShow (Sender: TObject);
    procedure FormActivate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  IncrementBuildNumber: TIncrementBuildNumber;

implementation

{$R *.dfm}

procedure TIncrementBuildNumber.FormShow (Sender: TObject);
var
  Resource_File_Contents:       TStringList;
  Full_File_Name_And_Path:      string;
  First_Line_Of_File:           string;
  Position_First_Dot:           Integer;
  Position_Second_Dot:          Integer;
  Position_Third_Dot:           Integer;
  Position_Trailing_Backslash:  Integer;
  Start_of_Build_Number:        Integer;
  Length_of_Build_Number:       Integer;
  Build_Number_In_ASCII:        string;
  Build_Number_Numeric:         Integer;
  Old_Resource_File_Name:       string;
  Success:      Boolean;
begin
  if (System.ParamCount <> 1) then
  begin
    ShowMessage ('Resource File name not in first command-line parameter.');
    Exit;
  end;

  Full_File_Name_And_Path := System.ParamStr(1);
  if (not TFile.Exists(Full_File_Name_And_Path, False)) then
  begin
    ShowMessage ('Resource file ' + Full_File_Name_And_Path + 
                 ' not found.');
    Exit;
  end;

  Resource_File_Contents := TStringList.Create;
  try
    Resource_File_Contents.LoadFromFile(Full_File_Name_And_Path);
    First_Line_Of_File := Resource_File_Contents.Strings[0];

    if (Copy(First_Line_Of_File, 1, 21) <> '#define CONST_VERSION') then
    begin
      ShowMessage ('First line of Version Info must start with "#define CONST_VERSION".' + 
                   #13 + 'Version not incremented.');
      Exit;
    end;

    Position_First_Dot := Pos('.', First_Line_Of_File);
    if (Position_First_Dot = 0) then
    begin
      ShowMessage ('Version must have format "a.b.c.d".' + #13 + 
                   'Build Number not incremented.');
      Exit;
    end;

    Position_Second_Dot := PosEx('.', First_Line_Of_File, 
                                 Position_First_Dot+1);
    if (Position_Second_Dot = 0) then
    begin
      ShowMessage ('Version must have format "a.b.c.d".' + #13 + 
                   'Build Number not incremented.');
      Exit;
    end;

    Position_Third_Dot := PosEx('.', First_Line_Of_File, 
                                Position_Second_Dot+1);

    if (Position_Third_Dot = 0) then
    begin
      ShowMessage ('Version must have format "a.b.c.d".' + #13 + 
                   'Build Number not incremented.');

      Exit;
    end;

    Position_Trailing_Backslash := PosEx('\', First_Line_Of_File, 
                                         Position_Third_Dot+1);

    if (Position_Trailing_Backslash = 0) then
    begin
      ShowMessage ('Version must have format "a.b.c.d\000".' + #13 + 
                   'Build Number not incremented.');
      Exit;
    end;

    Start_of_Build_Number  := Position_Third_Dot + 1;
    Length_of_Build_Number := Position_Trailing_Backslash - 
                              Start_of_Build_Number;

    if (Length_of_Build_Number < 1) then
    begin
      ShowMessage ('Build Number must be present.' + #13 + 
                   'Build Number not incremented.');
      Exit;
    end;

    Build_Number_In_ASCII := Copy (First_Line_Of_File, 
                                   Start_of_Build_Number, 
                                   Length_of_Build_Number);
    Success := TryStrToInt (Build_Number_In_ASCII, Build_Number_Numeric);
    if (not Success) then
    begin
      ShowMessage ('Build Number must be numeric integer.' + #13 + 
                   'Build Number not incremented.');
      Exit;
    end;

    Build_Number_Numeric := Build_Number_Numeric + 1;
    Build_Number_In_ASCII := IntToStr(Build_Number_Numeric);
    Resource_File_Contents.Strings[0] := Copy(First_Line_Of_File, 1, 
                                              Position_Third_Dot) +
                                              Build_Number_In_ASCII + 
                                              '\000"';
    Old_Resource_File_Name := Full_File_Name_And_Path;
    Old_Resource_File_Name := TPath.ChangeExtension(Old_Resource_File_Name, '~rc');

    if TFile.Exists(Old_Resource_File_Name, False) then
      TFile.Delete(Old_Resource_File_Name);

    Success := RenameFile(Full_File_Name_And_Path, Old_Resource_File_Name);
    if (not Success) then
    begin
      ShowMessage ('Error renaming old resource file to have extension "~rc".' + #13 + 
                  'Build Number not incremented.');
      Exit;
    end;

    Resource_File_Contents.SaveToFile(Full_File_Name_And_Path);
  finally
    Resource_File_Contents.Free;
  end;
end;

procedure TIncrementBuildNumber.FormActivate (Sender: TObject);
begin
  Close;
end;

end.

C) 在应增加内部版本号的项目的项目选项中:

  • 去掉勾选“包括版本信息”。

  • 使用以下文本添加预构建事件,如所写,包括两对双引号,替换 中的部分:

"" "<.rc>"

D) 添加到项目源代码中,就在“program”关键字的正下方:

{$R '<whatever you called it>.res' '<whatever you called it>.rc'} // I think both names must

在这里是一样的:IIRC,当它们不同时会出错。

E) 编译、运行并享受 Auto-Increment 内部版本号的返回,尽管 Embarcadero 已移除该设施。

Jim 的内容结束

您可以使用预构建事件来更新 ProductNameFileDescription 值,或任何其他必须不同于基本脚本的值。

【讨论】:

  • 非常有前途的解决方案,当我回到我的 IDE 时会尝试。
  • 我猜你需要编写 ProductName 的变体脚本。
  • 是的。在 EMBT Delphi 论坛上有一个应用程序的完整源代码(找不到链接,当然搜索在那里不起作用 - 如果我找到它会添加它)用于处理该部分的预构建事件。我在那里回答了几乎相同的问题,并进行了逐步的描述,并且提出问题的人很好地在他们的“谢谢”回复中发布了他们编写的应用程序的代码,以供其他人使用。
  • @BerndLinde:当然。我能够从 CodeNewsFast 检索原始帖子;我已经合并了大部分内容并提供了指向该存档版本的新链接。
  • @EdwinYip:该行是上一行注释的延续。您可以通过将其与我自己的上述资源副本进行比较来看到这一点。我已对其进行了更正,以便您更轻松。
【解决方案2】:

这是 dzPrepBuild 的用例之一: http://www.dummzeuch.de/delphi/dzprepbuild/englisch.html

(注:该项目已移至 sourceforge,因为去年 berlios 将被关闭。http://sourceforge.net/projects/dzprepbuild/

【讨论】:

    【解决方案3】:

    更新:它不是 RADStudio 本身的一部分,而是来自 Andreas Hausladen's DDevExtensions(我已经习惯了......!)。 p>

    只要您安装了来自 Andreas Hausladen 的优秀 DDevExtensions,您就可以在 IDE 中使用 ProjectGroup 来实现。

    • 有一个项目组来包含您的所有项目
    • 确保您的每个项目都在Options|Version Info page 中选中了“在项目中包含版本信息”。
    • 使用菜单Project|Set Versioninfo... 打开“设置项目版本信息”对话框(仅一次,当前项目无关紧要)。
    • 在那里,您可以指定所有版本信息并选择“应用于所有”或仅应用于选定项目(如果您选中“应用于选定项目”)。

    例如,看看我如何同时为两个项目设置版本:

    然后,ProjectGroup 上的Build All 生成了两个 exe,版本设置为 1.1.1.9 和所有其他详细信息...

    【讨论】:

    • 这是一个更好的解决方案,内置于 RAD Studio
    • 我们目前有大约 40 个应用程序在生产中。您的建议是将它们全部放在一个项目组中,只是为了不必从您的 VCS 中签出版本资源脚本文件? (不反对;只是询问。)
    • 如果您从 IDE 构建,这非常好。对于大多数项目来说,编写构建过程的脚本通常会更好。
    • @KenWhite,我当然不建议将 40 个具有相同版本信息的文件从一个巨大的项目组开始或管理,但对于您可以有效使用项目的项目组,然后您也可以利用它从同一中心位置管理版本信息。 VCS 将存储对项目所做的更改以及任何其他更改。
    • @DavidHeffernan,并且可以从保存在 VCS 中的内容以其他方式/稍后/异步/自动处理构建。重点是您可以从 IDE 中管理版本信息。然后,您可以使用项目中的任何内容进行构建:新代码、新单元、新资源……
    猜你喜欢
    • 2014-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多