Search code examples
delphimsbuildcompilationversiondelphi-xe2

How to define application version in one place for multiple applications?


We have a system which consists of numerous applications. All applications have their version changed at the same time. Currently, when we release a new version, we have to manually open the project options of each application and change the version one by one. Is there any way to compile all applications on the same version, for example, save it in a global file and upon compilation, read this file and assign that version to the project? I'm just trying to eliminate too many steps, because we plan on changing the version numbers more frequently. I'd like to change it in one place only. Can this be done? and how?


Solution

  • You can create a VERSIONINFO resource, in a plain text file (eg., 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
    }
    }
    

    Note: The C-style null terminators (\0) are needed at the end of each item as shown in order for the resource compiler to properly terminate the strings. Otherwise, when you use Explorer to display the version information for the executable you may get garbled or partially concatenated values.

    Add a line to your project source file:

    {$R VersionInfo.res VersionInfo.rc}
    

    I suggest putting the common version info resource into an externals reference in your version control system, and then you can just check it out into each project's folder and update it easily.

    Do a Project->Build, and your version info is embedded in the .exe. You can verify by using Windows Explorer and viewing the properties of your app.

    There's a couple of posts (one by me and one in a response by Jim Fleming) in the Embarcadero Delphi forums at CodeNewsFast archives. Mine is [here], where, I describe step-by-step how to use a pre-build event in your project to update the version number in the resource script I posted above.

    Jim posts a few replies, but about a dozen posts or so down there's source for an executable that can be called from the pre-build event that works for him. (There are some things I'd do differently, like letting the IDE pass the project name and location on the command line; how to do so is described in the step-by-step article. I'd also handle the version parsing and incrementing differently, but the basic app is a good starting location.)

    Embarcadero's groups are currently down, but I was able to retrieve Jim's code from CodeNewsFast as well, and can reproduce it here:

    Ken,

    Thanks to you, I got it to work.

    Just in case anyone else wants to implement this solution, Below you will find the necessary steps and auxiliary program.

    Jim Fleming

    A) Create your Version Info resource file in your project directory or wherever, with the following

    contents, and file extension .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) Create a new project in some folder, code of only module should be similar to:

    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) In Project Options of the project whose build number should be incremented:

    • Remove the tick "include version info".

    • Add a pre-build event with the following text, as written, including the two pairs of double-quotes, substituting the parts within < >:

    "<full file name and path of the auto-increment program exe>" "<full file name and path of the .rc resource file>"

    D) Add to the project source, right below the "program" keyword:

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

    be the same here: IIRC, got errors when they were different.

    E) Compile, run and enjoy the return of Auto-Increment build numbers, despite Embarcadero's having removed the facility.

    End of Jim's content

    You could use the pre-build event to, for instance, update the ProductName or FileDescription values, or any others that have to be different from the base script.