Delphi 7 on crutches: automating resource provisioning

Epigraph: "Let this inspire you to feat!" (Bel Kaufman, Up the Downstairs).





About crutches and bicycles, an integral part of modern necromancy.





This is the story of integrating one single solution into the development process. The solution has been brought to the final result, the link to the repository will be further in the text.





It would seem, what could be simpler and more natural than transparent, without "ridiculous body movements", work with resources placed in a specially allocated folder for this? What if the development environment is released at the millennium border?





, , «» Delphi 7, SQL . ( , -), , — « ». , … , SSMS, . , Delphi, SQL , .





«» : ! ( ) , . , , *.rc , .





, - IDE Delphi 7 - . , . MS Build, - ! , , , - IDE .





, (*.rc) , . , , ( ), . rc- ! , . 





. , , . , «-» , ( - ). , ( SQL) . , , ( ), … , - , .





.dfm — « ». , , … , ? ( ). .





  object SqlSALES: TSqlVar
    SQL.Strings = (
      'SET NOCOUNT ON'
      ''
      'IF OBJECT_ID(N'#39'tempdb..#Sales'#39', N'#39'U'#39') IS NOT NULL '
      'DROP TABLE #Sales;'
      ''
      'IF OBJECT_ID(N'#39'tempdb..#ClientIDs'#39', N'#39'U'#39') IS NOT NULL '
      'DROP TABLE #ClientIDs;'
      ''
      '-- '#1055#1072#1088#1072#1084#1077#1090#1088#1099': '#1076#1072#1090#1072' '#1086#1090', '#1076#1072#1090#1072' '#1076#1086', '#1075#1088#1091#1087#1087#1072
      '-- '#1056#1077#1072#1083#1080#1079#1072#1094#1080#1103
      'SELECT '
      
        #9'OperDate,                                                  -- '#1044 +
        #1072#1090#1072
      
        ...
      
      



, : , IDE, . , , . , , Delphi, , - . — ...









, - , - .





, . :









  1. !!! , ( build)





  2. .





  3. — . « ».





Gulp.js — , . . .   .





. -, , , .rc . Delphi. -, IDE, , (-, , , IDE ). —  IDE .





( ) , USES, :





{%File 'Res\SRC\SQL\Import\Sectors\leafSectors.sql'}
      
      



, .





, . :





SomeProject.dpr





library SomeProject;
{
         !

           
     . 

      res/CompileRc/CompileAllResources.cmd

      _ .cmd
    ,        res\src
    res/CompileRc/CompileAllResources.cmd

       ,    
      'Res\AutoGenerated.rc'. <=  
   -  -----^^^^^^^^^^^^^^^^^^^^   Ctrl + Enter
}
{<AUTOGENERATED_RC>}
{%File 'Res\SRC\SQL\DB_Updates.sql'}
{%File 'Res\SRC\SQL\Foo\Sales.sql'}
{%File 'Res\SRC\SQL\Foo\Stocks.sql'}
...
{$R 'Res\AutoGenerated.res' 'Res\AutoGenerated.rc'}
{</AUTOGENERATED_RC>}

uses
...
      
      



AutoGenerated.rc ():





SQL_DB_Updates         RCDATA  PREPARED\SQL_DB_Updates
SQL_Foo_Sales          RCDATA  PREPARED\SQL_Foo_Sales
SQL_Foo_Stocks         RCDATA  PREPARED\SQL_Foo_Stocks
...
      
      



:





gulpfile.js





const { watch, series } = require('gulp');
const spawn = require('child_process').spawn;
 
function compileResources(cb) {
 var cmd = spawn('CompileRc\\CompileAllResources.cmd', [], {stdio: 'inherit'});
 cmd.on('close', function (code) {
   cb(code);
 });
}
 
exports.default = function() {
 compileResources(code=>{})
 watch('Src/**', compileResources); // series(compileResources, ...)
};
      
      



, , ( ). . 





, , . :





>gulp --version
CLI version: 2.3.0
Local version: 4.0.2
>node -v
v12.20.0
      
      



, —  .





:









  • ,





  • Perl, , , rc-, ()









  • dpr . ,





  • rc-





  • ( .





.





CompileAllResources.cmd





@Echo off

set BatchDir=%~dp0
cd %BatchDir%
touch ..\AutoGenerated.rc

FOR %%i IN ("%BatchDir%..") DO (set target=%%~fi)
echo.
echo [%TIME%] STARTING: ===== %target% =====
echo.

if not exist %BatchDir%..\prepared\*.* md %BatchDir%..\prepared > nul
call %BatchDir%..\AutoCompileRc.Config.cmd
FOR %%i IN ("%DprFile%") DO (set DprFileOnly=%%~nxi)

call %BatchDir%WaitWhileRunned.cmd git

cd %BatchDir%..
%BatchDir%bin\find SRC -type f | %BatchDir%bin\grep -E -v --file=%BatchDir%excludes.lst>%BatchDir%ResFiles.lst
%BatchDir%bin\perl %BatchDir%CreateRc.pl %BatchDir%ResFiles.lst AutoGenerated.rc %BatchDir%RcSources.lst %BatchDir%PrepareIt.cmd
cd %BatchDir%
echo {$R 'Res\AutoGenerated.res' 'Res\AutoGenerated.rc'}>>RcSources.lst

call between.cmd %DprFile% "\{<AUTOGENERATED_RC>\}\s*" "\{<\/AUTOGENERATED_RC>\}">RcSourcesOld.lst
fc RcSources.lst RcSourcesOld.lst>nul
if errorlevel 1 (
	call ReplaceBetween.cmd %DprFile% RcSources.lst "\{\<AUTOGENERATED_RC\>\}\s*" "\{\<\/AUTOGENERATED_RC\>\}">%DprFileOnly%.tmp
	copy %DprFileOnly%.tmp %DprFile%>nul
	del %DprFileOnly%.tmp
)
echo Preparing updated and new files...
cd ..
call %BatchDir%PrepareIt.cmd
echo Compiling resources...
brcc32 AutoGenerated.rc
cd %BatchDir%
echo.
echo [%TIME%] DONE: ===== %target% =====
echo.
      
      



, find grep ( git) . , . , . ( « ») :





CreateRc.pl





#!c:/Perl/bin/perl
open (IN, "<".$ARGV[0]) || die $!;      #     
open (RC_ENC, ">".$ARGV[1]) || die $!;  #   (.rc)
open (LST, ">".$ARGV[2]) || die $!;     #    
  # ,       
  # {<AUTOGENERATED_RC>}  {</AUTOGENERATED_RC>}
open (ENC_CMD, ">".$ARGV[3]) || die $!; #    (.cmd)
  #      (, )
while(<IN>){
  split /\n/;
  $File = $_;                     #     
  $File =~ s/\//\\/g;             # -   
  $File =~ s/\n//;                # -   
  $Name = $File;                  #  
  $Name =~ s/^Src\\//i;           # -   ()
  $Name =~ s/\\/_/g;              # -    — 
  $Name =~ s/\..*$//;             # -  
  $Dest = "PREPARED\\".$Name."";
  $_ = $File;
  $EncryptIt = ! /\\Bin\\/i;      #      bin  
  print RC_ENC $Name
    , substr("                                        ", 1, 40 - length($Name))
    , "    RCDATA  "
    , $Dest
    , "\r\n";
  $_ = $File;
  if (! /\\Bin\\/i) {
    #      (:    dpr )
    #      bin        
    print LST
        "{\%File 'Res\\"
      , $File
      , "'}\r\n";
  }
  #      :    
  # (   )
  if (! (-f $Dest) || ( (stat $File)[9] > (stat $Dest)[9] ) ) {
    if ($EncryptIt) {
      # : encrypt.cmd <> <> <    >
      #        .
      # ncrypt.cmd  .
      print ENC_CMD "call encrypt.cmd "
        , $File
        , substr("                                        ", 1, 40 - length($File))
        , " "
        , $Dest
        , " \""
        , lc $Name
        , "\"\r\n";
    } else {
      print ENC_CMD "copy "
        , $File
        , substr("                                        ", 1, 40 - length($File))
        , " "
        , $Dest
        , ">nul\r\n";
    }
  }
}
close IN;
close RC_ENC;
close LST;
close ENC_CMD;

      
      



. , git ( ) -   . , grep , . Find - ( , , , , ). — , , , , . - — , .





, , — , IDE.





, , - . ( , ) , ( node.js), ?





— FolderMonitor, Delphi (https://bitbucket.org/danik-ik/foldermonitor/src/master/). , (, , , . https://bitbucket.org/danik-ik/compilerc/src/master/README.md).





CompileRc git. . (), « ». SmartGit, CompileRc ( CompileRc, , ), ( ):





, . , , , — . , (// ).





:





(******************************************************************************
   (  )   , 
   .

      :
  https://webdelphi.ru/2011/08/monitoring-izmenenij-v-direktoriyax-i-fajlax-sredstvami-delphi-chast-1/

   :
  -     
  -  
  -    
 ******************************************************************************)
unit FolderMonitorCore;

interface

uses Classes, Windows, SysUtils;

type
  TFolderMonitorCore = class(TThread)
    private
      FDirectory: string;
      FScanSubDirs: boolean;
      FOnChange   : TNotifyEvent;
      procedure DoChange;
    public
      constructor Create(ASuspended: boolean; ADirectory:string; AScanSubDirs: boolean);
      property OnChange: TNotifyEvent read FOnChange write FOnChange;
    protected
      procedure Execute; override;
  end;

implementation

{ TFolderMonitorCore }

constructor TFolderMonitorCore.Create(ASuspended: boolean; ADirectory: string;
  AScanSubDirs: boolean);
begin
  inherited Create(ASuspended);
  FDirectory:=ADirectory;
  FScanSubDirs:=AScanSubDirs;
  FreeOnTerminate:=true;
end;

procedure TFolderMonitorCore.DoChange;
begin
  if Assigned(FOnChange) then
    FOnChange(Self);
end;

procedure TFolderMonitorCore.Execute;
var ChangeHandle: THandle;
begin
  //   ,   
  ChangeHandle:=FindFirstChangeNotification(PChar(FDirectory),
                                            FScanSubDirs,
                                            FILE_NOTIFY_CHANGE_FILE_NAME+
                                            FILE_NOTIFY_CHANGE_DIR_NAME+
                                            FILE_NOTIFY_CHANGE_SIZE+
                                            FILE_NOTIFY_CHANGE_LAST_WRITE
                                            );
  //   ,   
{$WARNINGS OFF}
  Win32Check(ChangeHandle <> INVALID_HANDLE_VALUE);
{$WARNINGS ON}
  try
    //        
    while not Terminated do
    begin
      {     :   ,
            }
      case WaitForSingleObject(ChangeHandle, 1000) of
        WAIT_FAILED: Terminate; {,  }
        WAIT_OBJECT_0: //  
          begin
            //  —        
            //    
            // (    Sublime Text  ).
            //     :   
            //   ,      .
            //      .
            sleep(5);
            WaitForSingleObject(ChangeHandle, 1); //   ,    
            //   :      
            if not Terminated then
              Synchronize(DoChange);
          end;
        WAIT_TIMEOUT: {DO NOTHING}; //    ,
                                    //      
      end;
      FindNextChangeNotification(ChangeHandle);
    end;
  finally
    FindCloseChangeNotification(ChangeHandle);
  end;
end;

end.
      
      



. . , . (, VS Code). , , ( , Delphi, rc-). Delphi AutoGenerated.rc. Delphi, rc- , . . , , : ( ) → → Delphi → Reload? Yes! → . .









, «» , - . , , ( ), - . , .





— , , .





P.S. 





, , , : « », . 





UPD:





I was asked in PM about the compiled FolderMonitor. If you have nothing to compile with, you can take it here: https://disk.yandex.ru/d/VTbuGvB5jabD6w





Perhaps it was left behind the scenes: this solution only generates code valid for Delphi and automates the routine : it timely adds a resource file to the compilation list and to the list of external project files, and timely compiles rc to RES (using Delphi means). Everything. Encrypts in a specific project, but this is just an overkill in the general case. By running it under the change monitor, I move the work with resources from the static state ("created once and for all") to the dynamic ("editing on the fly").








All Articles