Delphi 7 con muletas: automatización del aprovisionamiento de recursos

Epígrafe: "¡Deja que esto te inspire a realizar hazañas!" (Bel Kaufman, Up the Downstairs).





Sobre muletas y bicicletas, una parte integral de la nigromancia moderna.





Esta es la historia de la integración de una única solución en el proceso de desarrollo. La solución se ha llevado al resultado final, el enlace al repositorio estará más adelante en el texto.





Parecería, ¿qué podría ser más simple y más natural que transparente, sin "movimientos corporales ridículos", trabajar con recursos colocados en una carpeta especialmente asignada para esto? ¿Qué pasa si el entorno de desarrollo se libera en la frontera del milenio?





, , «» 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:





Me preguntaron en PM sobre el FolderMonitor compilado. Si no tiene nada con qué compilar, puede hacerlo aquí: https://disk.yandex.ru/d/VTbuGvB5jabD6w





Quizás se quedó detrás de escena: esta solución solo genera código válido para Delphi y automatiza la rutina : agrega oportunamente un archivo de recursos a la lista de compilación y a la lista de archivos de proyecto externos, y compila oportunamente rc a RES (usar Delphi significa ). Todo. También encripta en un proyecto específico, pero esto es simplemente una exageración en el caso general. Al ejecutarlo bajo el monitor de cambios, muevo el trabajo con recursos del estado estático ("creado de una vez por todas") al dinámico ("edición sobre la marcha").








All Articles