如何在一个地方为多个应用程序定义应用程序版本?

20

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


我使用自己开发的工具为每个应用程序创建.rc文件,然后在我的构建脚本中将其编译为资源。换句话说,我不依赖于任何内置设施。 - David Heffernan
@MarcusAdams 包括产品版本和文件版本。 - Jerry Dodge
1
如果你使用像FinalBuilder这样的构建应用程序,你可以很容易地自动化这个过程。我在每个应用程序的主窗体顶部使用了一个已注释的名称/值对,例如{ buildversion=3.0.1 },然后FinalBuilder读取名称值、递增并保存,同时创建我的部署构建。 - John Easley
3个回答

29
您可以创建一个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资源管理器查看应用程序的属性来验证。

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

Jim发表了一些回复,但是在大约十二个帖子之后,他发布了一个可从预构建事件中调用的可执行文件的源代码,该代码对他有效。(有一些事情我会做得不同,例如让IDE通过命令行传递项目名称和位置;如何执行在步骤说明中有描述。我也会以不同的方式处理版本解析和增量,但基本应用程序是一个很好的起点。)

Embarcadero的群组目前无法使用,但我已经从CodeNewsFast中检索到Jim的代码,并在此处重现:

Ken,

多亏了你,我让它正常工作了。

以防其他人想要实施这个解决方案,下面是必要的步骤和辅助程序。

Jim Fleming

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) 在应该增加版本号的项目的“项目选项”中:

  • 取消“包括版本信息”的勾选。

  • 添加一个预构建事件,文本如下,按原样书写,包括两对双引号,用< >替换其中的部分:

"<自动增量程序exe的完整文件名和路径>" "<.rc资源文件的完整文件名和路径>"

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

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

在这里保持一致:如果它们不同,我记得会出现错误。

E)编译、运行并享受自动递增构建号的返回,尽管 Embarcadero 已经删除了该功能。

Jim 内容结束

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


1
非常有前途的解决方案,回到我的IDE后会尝试。 - Jerry Dodge
@KenWhite,由于Embarcadero正在关闭他们的旧论坛(目前由于证书问题无法访问),如果您在其他地方仍然有写下步骤的记录,是否可以总结一下逐步过程? - Bernd Linde
1
@BerndLinde:当然可以。我已经从CodeNewsFast中检索到原始帖子;我已经整合了大部分内容,并提供了该存档版本的新链接。 - Ken White
@JohnKouraklis:它适用于Windows应用程序(Win32/64目标),但不适用于其他平台。 - Ken White
1
@EdwinYip:那行是上一行注释的延续。通过将其与我手头的资源副本进行比较,您可以看到这一点。我已经进行了更正,以使您更容易理解。 - Ken White
显示剩余6条评论

16

更新: 这个功能不是RADStudio自带的,而是来自于Andreas Hausladen的DDevExtensions(我已经习惯使用它了...!)。

如果你安装了Andreas Hausladen出色的 DDevExtensions,你可以在IDE中使用ProjectGroup实现这个功能。

  • 创建一个Project Group包含所有项目
  • 确保每个项目都在Options|Version Info page中勾选了"Include version information in project"
  • 使用菜单Project|Set Versioninfo...打开"Set Project Versioninfo"对话框(只需要一次,当前项目无关紧要)。
  • 在这里,您可以指定所有版本信息,并选择哪些应该应用于所有项目或仅应用于所选择的项目(如果您勾选了"Apply to Selected")。

例如,看下面如何同时将版本设置为两个项目的示例: Example where I set version to both projects at once

然后,在ProjectGroup上执行Build All就能生成具有版本号1.1.1.9和其他详细信息的两个exe文件了...


这甚至是更好的解决方案,因为它是内置于RAD Studio中的。 - Jerry Dodge
我们目前有大约40个应用程序正在生产中。您的建议是将它们全部放在一个项目组中,只是为了不必从版本控制系统中检出版本资源脚本文件吗?(不会投反对票,只是想问一下。) - Ken White
如果你从IDE构建,这很不错。对于大多数项目来说,脚本化构建过程通常更好。 - David Heffernan
1
@DavidHeffernan,构建可以通过VCS中保存的方式进行其他处理/稍后/异步/自动化处理。主要是要从IDE中“管理”版本信息。然后使用现在包含在项目中的任何内容来构建:新代码、新单元、新资源... - Francesca
@JerryDodge,至少是XE和XE2。截图来自XE。 - Francesca
显示剩余4条评论

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接