spiegel-keyman/common/windows/delphi/tools/devtools/DevUtils.pas
Marc Durdin ad9ab53c72 feat(windows): improve startup time by caching locale names
This was flagged years ago as a potential performance hotspot: each
locale is loaded at process start for any kmcomapi-involved process,
which takes quite a long time. This refactor moves the locale
enumeration out of kmcomapi and into the build process, so we have a
static list of locales put into locale/index.xml.

This includes a minor breaking change to Keyman Engine API on Windows:
`MessageFromID(id, locale)` now only allows the three ids
SKUILanguageName, SKUILanguageNameWithEnglish, and SKLanguageCode, for
performance reasons. At this point, only SKUILanguageNameWithEnglish is
actually used anywhere in Keyman, and it is unlikely that any other
consumers are requesting alternate IDs.

Fixes: #14787
Build-bot: skip release:windows
2025-09-19 14:11:37 +02:00

263 lines
8.4 KiB
ObjectPascal

(*
Name: DevUtils
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Create Date: 4 May 2012
Modified Date: 13 Sep 2016
Authors: mcdurdin
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
History: 04 May 2012 - mcdurdin - I3307 - V9.0 - Delphi XE2 path and package manager
25 May 2012 - mcdurdin - I3339 - V9.0 - Add GUI compiler wrapper for quicker review of hints and warnings
26 Jun 2012 - mcdurdin - I3378 - KM9 - Delphi compiler wrapper needs quiet mode
02 Jan 2013 - mcdurdin - I3726 - V9.0 - Release build should check Debug key and SVN state
02 Jan 2013 - mcdurdin - I3726 - V9.0 - Release build should check Debug key and SVN state
13 Sep 2016 - mcdurdin - I5087 - Move from SVN to GIT for Keyman source
*)
unit DevUtils; // I3307 // I3339
interface
procedure DevLog(const s: string; nl: Boolean);
procedure Run;
function CommandExecute(const cmdline, logfname, curdir: string; sw: Integer; var ecode : integer; echolog: Boolean = False; cleanlog: boolean = False): Boolean; // I8935
implementation
uses
System.Classes,
System.SysUtils,
Winapi.Windows,
Winapi.ActiveX,
DevCheckGitStatus, // I5087
DevInstallPackages,
DevIncludePaths,
DevReleaseBuildCheck,
Keyman.System.DevTools.BuildLocaleIndex,
Keyman.System.DevTools.BuildMessageConstants,
Keyman.System.DevTools.BuildSetupStringTranslations,
RegistryKeys;
procedure DevLog(const s: string; nl: Boolean);
begin
if nl
then writeln(s)
else write(s);
end;
procedure Run;
var
Success: Boolean;
begin
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
try
// Parse Command Line
//
// -ip package.bpl : install package
// -rp : reset packages
// -ai path[;path...] : add include path(s)
// -ri : reset include paths
if ParamCount < 1 then
begin
writeln('Usage: devutils <command>'); // I3339 // I3378
writeln(' -ip package.bpl : install package');
writeln(' -rp : reset packages');
writeln(' -ai path[;path...] : add include path(s)');
writeln(' -ri : reset include paths');
writeln(' -ti : touch PathDefines.mak (create if not present)'); //TODO: remove as unused
writeln(' -rt : check release build prereqs, e.g. HKCU\'+SRegKey_KeymanDebug_CU); // I3726
writeln(' -git : check git repository commit/update status'); // I3726 // I5087 //TODO: remove as unused
writeln(' -buildmessageconstants <strings.xml> <messageidentifierconsts.pas>: build MessageIdentifierConsts.pas from current strings.xml');
writeln(' -buildsetupstrings <sourcepath> <destinationpath>: build Keyman.Setup.System.Locale.*.pas from strings.xml found in sourcepath');
writeln(' -buildlocaleindex <locale-folder> <destination.xml>: build index.xml from strings.xml found in locale-folder');
ExitCode := 1;
Exit;
end;
if (ParamStr(1) = '-ip') and (ParamCount = 2) then
Success := TInstallPackages.InstallPackage(ParamStr(2))
else if (ParamStr(1) = '-rp') and (ParamCount = 1) then
Success := TInstallPackages.Reset
else if (ParamStr(1) = '-ai') and (ParamCount = 2) then
Success := TIncludePaths.Add(ParamStr(2))
else if (ParamStr(1) = '-ri') and (ParamCount = 1) then
Success := TIncludePaths.Reset
else if (ParamStr(1) = '-ti') and (ParamCount = 1) then
Success := TIncludePaths.Touch
else if (ParamStr(1) = '-rt') then // I3726
Success := TReleaseBuildCheck.Run
else if (ParamStr(1) = '-git') then // I3726 // I5087
Success := TCheckGitStatus.Run
else if (ParamStr(1) = '-buildmessageconstants') and (ParamCount = 3) then
Success := TBuildMessageConstants.Run(ParamStr(2), ParamStr(3))
else if (ParamStr(1) = '-buildsetupstrings') and (ParamCount = 3) then
Success := TBuildSetupStringTranslations.Run(ParamStr(2), ParamStr(3))
else if (ParamStr(1) = '-buildlocaleindex') and (ParamCount = 3) then
Success := TBuildLocaleIndex.Run(ParamStr(2), ParamStr(3))
else
begin
writeln('Invalid parameters');
ExitCode := 2;
Exit;
end;
if Success
then ExitCode := 0
else ExitCode := 3;
finally
CoUninitialize;
end;
end;
function CommandExecute(const cmdline, logfname, curdir: string; sw: Integer; var ecode : integer; echolog: Boolean = False; cleanlog: boolean = False): Boolean; // I8935
var
si: TStartupInfo;
b, ec: DWord;
buf: array[0..512] of ansichar;
SecAttrs: TSecurityAttributes;
hsoutread, hsoutwrite: THandle;
hsinread, hsinwrite: THandle;
pi: TProcessInformation;
//flog : TextFile;
FLogText: string;
locallog : boolean;
s, str: string;
loglist: TStringList;
n: Integer;
vcmdline: PChar;
begin
//locallog := False;
Result := False;
ecode := -1;
FillChar(SecAttrs, SizeOf(SecAttrs), #0);
SecAttrs.nLength := SizeOf(SecAttrs);
SecAttrs.lpSecurityDescriptor := nil;
SecAttrs.bInheritHandle := TRUE;
if not CreatePipe(hsoutread, hsoutwrite, @SecAttrs, 0) then Exit;
FillChar(SecAttrs, SizeOf(SecAttrs), #0);
SecAttrs.nLength := SizeOf(SecAttrs);
SecAttrs.lpSecurityDescriptor := nil;
SecAttrs.bInheritHandle := TRUE;
if not CreatePipe(hsinread, hsinwrite, @SecAttrs, 0) then
begin
CloseHandle(hsoutread);
CloseHandle(hsoutwrite);
Exit;
end;
{ See support.microsoft.com kb 190351 }
n := 0;
FLogText := '';
try
locallog := (logfname<>'');
if locallog then
begin
loglist := TStringList.Create;
//AssignFile(flog,logfname);
//Rewrite(flog);
if not cleanlog then loglist.Add(cmdline); // I8935
end
else
loglist := nil;
vcmdline := AllocMem((Length(cmdline)+1)*sizeof(char));
try
si.cb := SizeOf(TStartupInfo);
si.lpReserved := nil;
si.lpDesktop := nil;
si.lpTitle := nil;
si.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
si.wShowWindow := sw;
si.cbReserved2 := 0;
si.lpReserved2 := nil;
si.hStdInput := hsinread;
si.hStdOutput := hsoutwrite;
si.hStdError := hsoutwrite;
StrCopy(vcmdline, PWideChar(cmdline));
//vcmdline := cmdline;
if CreateProcess(nil, PChar(vcmdline), nil, nil, True, HIGH_PRIORITY_CLASS, nil, PChar(curdir), si, pi) then
begin
if GetExitCodeProcess(pi.hProcess, ec) then
begin
while ec = STILL_ACTIVE do
begin
Sleep(20);
Inc(n);
if n = 30 then
begin
{Application.ProcessMessages;
if FCancelCommand then
begin
TerminateProcess(pi.hProcess, 0);
ec := 0;
Exit;
end;}
n := 0;
end;
PeekNamedPipe(hsoutread, nil, 0, nil, @b, nil);
if b > 0 then
begin
ReadFile(hsoutread, buf, High(buf), b, nil);
s := String(Copy(buf, 1, b));
if locallog then str := str + s;
if echolog then DevLog(s, False);
end;
if not GetExitCodeProcess(pi.hProcess, ec) then ec := 0;
end;
end;
repeat
PeekNamedPipe(hsoutread, nil, 0, nil, @b, nil);
if b > 0 then
begin
ReadFile(hsoutread, buf, High(buf), b, nil);
s := String(Copy(buf, 1, b));
if locallog then str := str + s;
if echolog then DevLog(s, False);
end;
until b = 0;
if echolog then DevLog('', True);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
end
else
Exit;
if locallog then
begin
loglist.Add(str);
if not cleanlog then loglist.Add(#13#10'exit code = ' + IntToStr(ec)); // I8935
loglist.SaveToFile(logfname, TEncoding.Default);
end;
finally
loglist.Free;
FreeMem(vcmdline);
end;
Result := True;
finally
if echolog then DevLog('exit code ='+IntToStr(ec),True);
ecode := ec;
//if locallog then CloseFile(flog);
CloseHandle(hsoutread);
CloseHandle(hsoutwrite);
CloseHandle(hsinread);
CloseHandle(hsinwrite);
end;
end;
end.