Skip to content

Commit e4f65a0

Browse files
authored
Fix lazbuild-backend CI: propagate defines, GUI toggle, GTK2, dedup (#102)
Bring the CI make driver in line so the lazbuild backend behaves like the fpc backend, and so the GUI test project no longer breaks non-Linux jobs. - Propagate MAKE_DEFINES to dependency packages under lazbuild. lazbuild applies --opt only to the top-level project; its --recursive rebuild compiles required packages with their own options, so the defines never reached them. Inject the defines into the fpc.cfg found via `fpc -va` before building and strip it afterwards, so every fpc invocation lazbuild spawns inherits them. Gated on the lazbuild backend with a non-empty MAKE_DEFINES; fail-fast if fpc.cfg cannot be located. The redundant lazbuild --opt define path is removed. No-op on the fpc backend, which already injects -dNAME per compile. - Install GTK2 dev libraries for the GUI link. Under lazbuild the LCL/GUI test project links against the GTK2 widgetset, whose dev libs are not preinstalled on the GitHub runners. Add ci_install_lcl_gui_deps (libgtk2.0-dev), called from native-build.sh and double-gated on the lazbuild backend and Linux. - Add the MAKE_LAZBUILD_GUI toggle (default false). Only meaningful under lazbuild - the fpc backend has no widgetset and always skips GUI projects. When false, GUI projects are skipped under lazbuild too, opting out of the per-platform widgetset toolchain (GTK2/X11 on Unix, cocoa on macOS). Wired through every job like MAKE_BUILD_BACKEND. - Fold the two identical GUI-handling blocks in BuildAllProjects and RunBenchmarkProjects into a single TryHandleGuiProject helper. make.pas compiles clean; make.yml is valid; the fpc backend is unchanged.
1 parent 2c0b24d commit e4f65a0

4 files changed

Lines changed: 202 additions & 48 deletions

File tree

.github/workflows/ci/native-build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/shared/common.sh"
66
ci_init_paths
77

88
ci_openssl_hack
9+
ci_install_lcl_gui_deps
910
ci_build_standard

.github/workflows/ci/shared/common.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,19 @@ ci_run_make() {
166166
"$build_dir/make"
167167
}
168168

169+
# The lazbuild backend builds LCL/GUI projects as a compile-check (the fpc
170+
# backend skips them). On Linux those link against the GTK2 widgetset, whose dev
171+
# libraries are not preinstalled on the GitHub runners, so the GUI link fails.
172+
# Install them here. No-op on the fpc backend and off Linux (macOS/Windows ship
173+
# their native widgetset). GUI projects are built but never run.
174+
ci_install_lcl_gui_deps() {
175+
[ "${MAKE_BUILD_BACKEND:-}" = "lazbuild" ] || return 0
176+
[ "$(uname -s)" = "Linux" ] || return 0
177+
echo "installing GTK2 dev libraries for LCL GUI project build (lazbuild/Linux)" >&2
178+
sudo apt-get update
179+
sudo apt-get install -y libgtk2.0-dev
180+
}
181+
169182
ci_build_standard() {
170183
ci_install_toolchain
171184
ci_export_toolchain_path

.github/workflows/make.pas

Lines changed: 174 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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]);
686704
end;
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;
16271635
end;
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+
16291750
function TMakeRunner.RepoRoot: string;
16301751
var
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');
17421869
end;
17431870

17441871
procedure TMakeRunner.UpdateSubmodules;
@@ -1934,9 +2061,11 @@ procedure TMakeRunner.RegisterAllPackagesLazbuild(const ASearchDir: string);
19342061
ForEachLpkInDir(ASearchDir, @RegisterPackageLazbuild);
19352062
end;
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.
19402069
function TMakeRunner.LazbuildArgs(const ABaseFlags: array of string;
19412070
const APath: string): TStringList;
19422071
var
@@ -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);
19502078
end;
19512079

@@ -2318,6 +2446,23 @@ procedure TMakeRunner.BuildGuiProject(const ALpiPath: string);
23182446
Log(CSI_Green, 'built GUI project (not run) ' + BinaryPath);
23192447
end;
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+
23212466
procedure TMakeRunner.BuildAllProjects;
23222467
var
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;
24222554
end;

0 commit comments

Comments
 (0)