@@ -91,11 +91,10 @@ TLazXml = class
9191 const AStubFileName, AUnitOutDir: string);
9292 class procedure AppendProjectBuildArgs (AArgs: TStrings;
9393 const AMainSource, AUnitOutDir, ATargetBinary: string);
94- // Conditional defines, formatted per backend: fpc takes -dNAME directly on
95- // the argv (must precede the source); lazbuild forwards them to the compiler
96- // via repeatable --opt=-dNAME (its own -d means --skip-dependencies) .
94+ // Conditional defines for the fpc backend: -dNAME on the argv (must precede
95+ // the source). The lazbuild backend instead injects defines via fpc.cfg (see
96+ // TMakeRunner.InjectDefinesIntoFpcConfig) so recursive package builds see them .
9797 class procedure AppendFpcDefineArgs (const ADefines: TStrings; AArgs: TStrings);
98- class procedure AppendLazbuildDefineArgs (const ADefines: TStrings; AArgs: TStrings);
9998 private
10099 class function IsAbsolutePath (const S: string): Boolean;
101100 class function ExpandMacros (const S, AProjDir, AUnitOutDir, APkgOutDir,
@@ -228,7 +227,16 @@ TMakeRunner = class
228227 // builds and runs console benchmark projects under BenchmarkTargetFolder
229228 // after the test suite completes.
230229 FRunBenchmark: Boolean;
230+ // Selected via MAKE_LAZBUILD_GUI (defaults to True). Only meaningful under
231+ // the lazbuild backend - the fpc backend never builds GUI projects (it has
232+ // no widgetset). When False, LCL/GUI projects are skipped even under
233+ // lazbuild, letting a run opt out of the per-platform widgetset toolchain
234+ // the GUI link needs.
235+ FBuildGuiProjects: Boolean;
231236 FGraph: TPackageGraph;
237+ // Non-empty while a MAKE_DEFINES block is appended to this fpc.cfg (lazbuild
238+ // backend only); RestoreFpcConfig strips it back out.
239+ FInjectedFpcCfgPath: string;
232240 function ParseBackendEnv : TBuildBackend;
233241 function ParsePackageScopeEnv : TPackageScope;
234242 function ParseBoolEnv (const AName: string; ADefault: Boolean): Boolean;
@@ -243,6 +251,7 @@ TMakeRunner = class
243251 procedure BuildAllProjects ;
244252 procedure RunBenchmarkProjects ;
245253 procedure BuildGuiProject (const ALpiPath: string);
254+ function TryHandleGuiProject (const ALpiPath, ASkipLabel: string): Boolean;
246255 function BuildProject (const ALpiPath: string): string;
247256 function BuildProjectWithLazbuild (const APath: string): string;
248257 function BuildProjectWithFpc (const APath: string): string;
@@ -284,6 +293,9 @@ TMakeRunner = class
284293 function RunFpcInfoProbeWithRetry (const AInfoFlag: string;
285294 out AValue: string): Boolean;
286295 procedure PrepareProjectBuild (Proj: TLpiProject);
296+ function LocateFpcConfig : string;
297+ procedure InjectDefinesIntoFpcConfig ;
298+ procedure RestoreFpcConfig ;
287299 public
288300 constructor Create;
289301 destructor Destroy; override;
@@ -317,6 +329,12 @@ TMakeRunner = class
317329 OPMBaseUrl = ' https://packages.lazarus-ide.org/' ;
318330 GitHubArchiveBaseUrl = ' https://github.com/' ;
319331
332+ // Delimiters for the MAKE_DEFINES block appended to fpc.cfg under the lazbuild
333+ // backend (see InjectDefinesIntoFpcConfig). '#'-prefixed lines are fpc.cfg
334+ // comments, so a leftover block is inert.
335+ FpcCfgMarkerBegin = ' #--- make.pas MAKE_DEFINES begin (auto-generated) ---' ;
336+ FpcCfgMarkerEnd = ' #--- make.pas MAKE_DEFINES end (auto-generated) ---' ;
337+
320338 Dependencies: array of TDependency = (
321339 // Examples:
322340 // (Kind: TDependencyKind.OPM; Name: 'SimpleBaseLib'; Ref: ''),
@@ -685,17 +703,6 @@ class procedure TLazXml.AppendFpcDefineArgs(const ADefines: TStrings;
685703 AArgs.Add(' -d' + ADefines[I]);
686704end ;
687705
688- class procedure TLazXml.AppendLazbuildDefineArgs (const ADefines: TStrings;
689- AArgs: TStrings);
690- var
691- I: Integer;
692- begin
693- if not Assigned(ADefines) then
694- Exit;
695- for I := 0 to ADefines.Count - 1 do
696- AArgs.Add(' --opt=-d' + ADefines[I]);
697- end ;
698-
699706// ---------------------------------------------------------------------------
700707// TProjectFiles
701708// ---------------------------------------------------------------------------
@@ -1354,6 +1361,7 @@ constructor TMakeRunner.Create;
13541361 FPackageScope := TPackageScope.Required;
13551362 FErrorCount := 0 ;
13561363 FRunBenchmark := False;
1364+ FBuildGuiProjects := True;
13571365 // Honor the NO_COLOR convention (https://no-color.org): any value disables
13581366 // ANSI colors. GitHub Actions renders ANSI in its log viewer, so default on.
13591367 FUseColor := GetEnvironmentVariable(' NO_COLOR' ) = ' ' ;
@@ -1626,6 +1634,119 @@ function TMakeRunner.RunFpcInfoProbeWithRetry(const AInfoFlag: string;
16261634 Result := False;
16271635end ;
16281636
1637+ // Return the path of the main fpc.cfg the toolchain reads, by parsing the
1638+ // config trace `fpc -va` prints (it echoes each config file as it reads it).
1639+ // `fpc -va` exits non-zero here because no source is given, but RunCommandEx
1640+ // still captures the trace. Returns '' if no config path can be found.
1641+ function TMakeRunner.LocateFpcConfig : string;
1642+ var
1643+ Output, Line, Low, Cand: string;
1644+ Lines: TStringList;
1645+ I, P: Integer;
1646+ begin
1647+ Result := ' ' ;
1648+ RunCommandEx(' fpc' , [' -va' ], ' ' , False, Output);
1649+ Lines := TStringList.Create;
1650+ try
1651+ Lines.Text := Output;
1652+ for I := 0 to Lines.Count - 1 do
1653+ begin
1654+ Line := Lines[I];
1655+ Low := LowerCase(Line);
1656+ // Only trace lines that name a config file; skip option-echo lines.
1657+ if (Pos(' config file' , Low) = 0 ) and (Pos(' options from file' , Low) = 0 ) then
1658+ Continue;
1659+ P := Pos(' file ' , Low);
1660+ if P = 0 then
1661+ Continue;
1662+ Cand := Trim(Copy(Line, P + Length(' file ' ), MaxInt));
1663+ if (Length(Cand) >= 4 ) and
1664+ (LowerCase(Copy(Cand, Length(Cand) - 3 , 4 )) = ' .cfg' ) then
1665+ Exit(Cand); // first config read = the main fpc.cfg
1666+ end ;
1667+ finally
1668+ Lines.Free;
1669+ end ;
1670+ end ;
1671+
1672+ // lazbuild applies `--opt` compiler options to the top-level project only; its
1673+ // `--recursive` rebuild of required packages uses each package's own options,
1674+ // so MAKE_DEFINES never reaches the dependency packages that way (the fpc
1675+ // backend avoids this by compiling each package with -d directly). To match it,
1676+ // append the defines to fpc.cfg so every fpc invocation lazbuild spawns - the
1677+ // project and every recursive package build - inherits them. No-op on the fpc
1678+ // backend (which already injects per compile) and when no defines are set.
1679+ procedure TMakeRunner.InjectDefinesIntoFpcConfig ;
1680+ var
1681+ Cfg: string;
1682+ SL: TStringList;
1683+ I: Integer;
1684+ begin
1685+ FInjectedFpcCfgPath := ' ' ;
1686+ if (not UsesLazbuild) or (FDefines.Count = 0 ) then
1687+ Exit;
1688+
1689+ Cfg := LocateFpcConfig;
1690+ if (Cfg = ' ' ) or (not FileExists(Cfg)) then
1691+ raise Exception.Create(
1692+ ' lazbuild backend with MAKE_DEFINES set, but fpc.cfg could not be located ' +
1693+ ' to propagate the defines to package builds. Refusing to continue, since ' +
1694+ ' the defines would silently not reach the dependency packages. ' +
1695+ ' Use MAKE_BUILD_BACKEND=fpc or make fpc.cfg discoverable via `fpc -va`.' );
1696+
1697+ SL := TStringList.Create;
1698+ try
1699+ SL.LoadFromFile(Cfg);
1700+ SL.Add(FpcCfgMarkerBegin);
1701+ for I := 0 to FDefines.Count - 1 do
1702+ SL.Add(' -d' + FDefines[I]);
1703+ SL.Add(FpcCfgMarkerEnd);
1704+ SL.SaveToFile(Cfg);
1705+ finally
1706+ SL.Free;
1707+ end ;
1708+ FInjectedFpcCfgPath := Cfg;
1709+ Log(CSI_Yellow, Format(' injected %d define(s) into %s (lazbuild package propagation)' ,
1710+ [FDefines.Count, Cfg]));
1711+ end ;
1712+
1713+ // Strip the block InjectDefinesIntoFpcConfig appended, restoring fpc.cfg.
1714+ procedure TMakeRunner.RestoreFpcConfig ;
1715+ var
1716+ SL, Kept: TStringList;
1717+ I: Integer;
1718+ InBlock: Boolean;
1719+ begin
1720+ if (FInjectedFpcCfgPath = ' ' ) or (not FileExists(FInjectedFpcCfgPath)) then
1721+ Exit;
1722+ SL := TStringList.Create;
1723+ Kept := TStringList.Create;
1724+ try
1725+ SL.LoadFromFile(FInjectedFpcCfgPath);
1726+ InBlock := False;
1727+ for I := 0 to SL.Count - 1 do
1728+ begin
1729+ if SL[I] = FpcCfgMarkerBegin then
1730+ begin
1731+ InBlock := True;
1732+ Continue;
1733+ end ;
1734+ if SL[I] = FpcCfgMarkerEnd then
1735+ begin
1736+ InBlock := False;
1737+ Continue;
1738+ end ;
1739+ if not InBlock then
1740+ Kept.Add(SL[I]);
1741+ end ;
1742+ Kept.SaveToFile(FInjectedFpcCfgPath);
1743+ finally
1744+ Kept.Free;
1745+ SL.Free;
1746+ end ;
1747+ FInjectedFpcCfgPath := ' ' ;
1748+ end ;
1749+
16291750function TMakeRunner.RepoRoot : string;
16301751var
16311752 Seeds: array [0 ..1 ] of string;
@@ -1739,6 +1860,12 @@ procedure TMakeRunner.InitEnvironment;
17391860 Log(CSI_Yellow, ' run benchmark: true' )
17401861 else
17411862 Log(CSI_Yellow, ' run benchmark: false' );
1863+
1864+ FBuildGuiProjects := ParseBoolEnv(' MAKE_LAZBUILD_GUI' , True);
1865+ if FBuildGuiProjects then
1866+ Log(CSI_Yellow, ' build GUI projects (lazbuild): true' )
1867+ else
1868+ Log(CSI_Yellow, ' build GUI projects (lazbuild): false' );
17421869end ;
17431870
17441871procedure TMakeRunner.UpdateSubmodules ;
@@ -1934,9 +2061,11 @@ procedure TMakeRunner.RegisterAllPackagesLazbuild(const ASearchDir: string);
19342061 ForEachLpkInDir(ASearchDir, @RegisterPackageLazbuild);
19352062end ;
19362063
1937- // Assemble a lazbuild argv: base flags, then the configured defines as
1938- // --opt=-dNAME, then the target path. Centralizes define injection so project
1939- // and package builds stay consistent.
2064+ // Assemble a lazbuild argv: base flags, then the target path. MAKE_DEFINES are
2065+ // NOT passed here as --opt: lazbuild would apply them to the top-level target
2066+ // only, not to the packages it rebuilds via --recursive. They are injected into
2067+ // fpc.cfg instead (see InjectDefinesIntoFpcConfig) so every spawned fpc compile
2068+ // - project and recursive package builds - picks them up.
19402069function TMakeRunner.LazbuildArgs (const ABaseFlags: array of string;
19412070 const APath: string): TStringList;
19422071var
@@ -1945,7 +2074,6 @@ function TMakeRunner.LazbuildArgs(const ABaseFlags: array of string;
19452074 Result := TStringList.Create;
19462075 for Flag in ABaseFlags do
19472076 Result.Add(Flag);
1948- TLazXml.AppendLazbuildDefineArgs(FDefines, Result);
19492077 Result.Add(APath);
19502078end ;
19512079
@@ -2318,6 +2446,23 @@ procedure TMakeRunner.BuildGuiProject(const ALpiPath: string);
23182446 Log(CSI_Green, ' built GUI project (not run) ' + BinaryPath);
23192447end ;
23202448
2449+ // Handle a GUI (LCL) project: build it (compile-check only - GUI apps are never
2450+ // run, they need a display) when the lazbuild backend is active AND GUI building
2451+ // is enabled (MAKE_LAZBUILD_GUI); otherwise skip it - the fpc backend has no
2452+ // widgetset, and MAKE_LAZBUILD_GUI=false opts lazbuild out too. Returns True if
2453+ // ALpiPath was a GUI project, so the caller stops its normal processing for it.
2454+ // ASkipLabel is the log prefix used when skipping (e.g. 'skip GUI project ').
2455+ function TMakeRunner.TryHandleGuiProject (const ALpiPath, ASkipLabel: string): Boolean;
2456+ begin
2457+ Result := IsGUIProject(ALpiPath);
2458+ if not Result then
2459+ Exit;
2460+ if LclSupported and FBuildGuiProjects then
2461+ BuildGuiProject(ALpiPath)
2462+ else
2463+ Log(CSI_Yellow, ASkipLabel + ALpiPath);
2464+ end ;
2465+
23212466procedure TMakeRunner.BuildAllProjects ;
23222467var
23232468 List: TStringList;
@@ -2327,18 +2472,8 @@ procedure TMakeRunner.BuildAllProjects;
23272472 try
23282473 for Each in List do
23292474 begin
2330- if IsGUIProject(Each) then
2331- begin
2332- if not LclSupported then
2333- begin
2334- Log(CSI_Yellow, ' skip GUI project ' + Each);
2335- Continue;
2336- end ;
2337- // lazbuild backend: the full LCL is present, so compile the GUI project
2338- // to verify it builds. It is not run because GUI apps need a display.
2339- BuildGuiProject(Each);
2475+ if TryHandleGuiProject(Each, ' skip GUI project ' ) then
23402476 Continue;
2341- end ;
23422477
23432478 if IsTestProject(Each) then
23442479 RunTestProject(Each)
@@ -2373,16 +2508,8 @@ procedure TMakeRunner.RunBenchmarkProjects;
23732508 try
23742509 for Each in List do
23752510 begin
2376- if IsGUIProject(Each) then
2377- begin
2378- if not LclSupported then
2379- begin
2380- Log(CSI_Yellow, ' skip GUI benchmark project ' + Each);
2381- Continue;
2382- end ;
2383- BuildGuiProject(Each);
2511+ if TryHandleGuiProject(Each, ' skip GUI benchmark project ' ) then
23842512 Continue;
2385- end ;
23862513
23872514 if not IsBenchmarkProject(Each) then
23882515 begin
@@ -2414,9 +2541,14 @@ function TMakeRunner.Execute: Integer;
24142541 InitEnvironment;
24152542 Log(CSI_Cyan, ' using target directory: ' + TargetDirectory);
24162543 UpdateSubmodules;
2417- InstallDependencies;
2418- BuildAllProjects;
2419- RunBenchmarkProjects;
2544+ InjectDefinesIntoFpcConfig;
2545+ try
2546+ InstallDependencies;
2547+ BuildAllProjects;
2548+ RunBenchmarkProjects;
2549+ finally
2550+ RestoreFpcConfig;
2551+ end ;
24202552 ReportSummary;
24212553 Result := FErrorCount;
24222554end ;
0 commit comments