feat(freebsd): self-host on FreeBSD — runtime fixes + target-driven defines
Blaise now runs natively on FreeBSD and compiles+links working FreeBSD binaries
(fork/exec RTL sub-compiles, threaded incremental workers, internal assembler +
linker, freestanding runtime), and cross-compiles both directions (Linux<->
FreeBSD). Each of these was a real FreeBSD-only runtime bug exposed by running on
a real FreeBSD 14.3 kernel — invisible to host-side static checks:
* fork(2) child return: FreeBSD returns the child in %rax=parent-pid with
%edx=1 (child indicator); the raw leaf must zero %eax when %edx!=0, else the
forked child runs the parent branch and never execve's its target (exit 127).
* thread join teardown: the worker set its join word + umtx-WAKE while still on
its own stack, so pthread_join munmap'd the shared stack/CB region out from
under the still-live thread -> SIGSEGV. Fixed by passing the join word as
thr_exit(state): the kernel does the suword+umtx-wake AFTER the thread leaves
its stack, so the joiner only wakes once the stack is safe to free.
* argc stack pad word: FreeBSD 16-aligns the entry %rsp and inserts a zero pad
word below argc when the argv+envc pointer-count parity requires it, so argc
is at [rsp] or [rsp+8]. _BlaiseStartC now detects and skips the pad word;
previously an execve'd child with a different environment read the pad (0) as
argc and reported "--source is required".
Target-driven OS conditional compilation (self-hosting requires HostTarget to be
correct on the produced binary):
* The OS defines (LINUX/FREEBSD/WINDOWS/UNIX) now follow the resolved --target,
not the compiler's build host. SeedPredefines seeds the host OS as a default
(keeps direct-lexer callers/tests working) via compile-time IFDEF; the driver
injects the target's OS symbol and AddDefinesTo / the unit loader replace the
host-seeded OS symbols with it on every lexer (program + dep units).
* HostTarget() reads {$IFDEF FREEBSD} etc., so a blaise built with
--target freebsd bakes HostTarget=osFreeBSD — a FreeBSD-hosted blaise then
defaults (no --target) to producing FreeBSD binaries. Verified both
directions on the VM.
Usability:
* blaise.cfg now supports rtl-src=<dir> (relative resolved against the config
dir); a CLI --rtl-src overrides it. So a relocated binary can drop a
blaise.cfg with unit-path= + rtl-src= and need no path flags. Tests:
TConfigTests.TestParseRtlSrc_*.
* --help documents the previously-undocumented --rtl-src flag and the
blaise.cfg keys, and the --target line now shows the ACTUAL host default
(TargetName(HostTarget())) instead of a hardcoded linux-x86_64 — correct on
every host.
Verified: all four fixpoints green; full suite 4032 tests under QBE-built and
native-built runner; Linux self-host and Linux<->FreeBSD cross-compile confirmed;
FreeBSD self-host (blaise compiling+running a program) confirmed on FreeBSD 14.3.
This commit is contained in:
parent
db85aba08d
commit
0824ed8a6b
|
|
@ -93,12 +93,17 @@ begin
|
|||
WriteLn(FormatFlagLine('--output <path>', 'Output binary path'));
|
||||
WriteLn(FormatFlagLine('--unit-path <dir>',
|
||||
'Add directory to unit search path (repeatable)'));
|
||||
WriteLn(FormatFlagLine('--rtl-src <dir>',
|
||||
'RTL source directory (default: beside the binary; or $BLAISE_RTL_SRC).'));
|
||||
WriteLn(FormatFlagLine('',
|
||||
'Needed when the binary is moved away from its source tree.'));
|
||||
WriteLn(FormatFlagLine('--define <sym> | -d <sym>',
|
||||
'Define a conditional-compilation symbol (repeatable)'));
|
||||
WriteLn(FormatFlagLine('--backend <id>', BackendUsageLine()));
|
||||
WriteLn(FormatFlagLine('--target <os>-<cpu>',
|
||||
'linux-x86_64 (default), linux-i386, linux-arm64,'));
|
||||
WriteLn(FormatFlagLine('', 'freebsd-x86_64, windows-x86_64, macos-arm64'));
|
||||
'Cross-compile target (default: ' + TargetName(HostTarget()) + ', the host).'));
|
||||
WriteLn(FormatFlagLine('', 'linux-x86_64, linux-i386, linux-arm64, freebsd-x86_64,'));
|
||||
WriteLn(FormatFlagLine('', 'windows-x86_64, macos-arm64'));
|
||||
WriteLn(FormatFlagLine('--emit-ir', 'Print QBE IR to stdout and exit'));
|
||||
WriteLn(FormatFlagLine('--emit-asm',
|
||||
'Print native assembly to stdout (requires --backend native)'));
|
||||
|
|
@ -134,8 +139,10 @@ begin
|
|||
'Emit OPDF debug info (.opdf.s companion file)'));
|
||||
WriteLn('');
|
||||
WriteLn('Configuration:');
|
||||
WriteLn(' Unit search paths can also be set in blaise.cfg (one unit-path=<dir>');
|
||||
WriteLn(' per line). Searched next to the binary, then ~/.blaise.cfg.');
|
||||
WriteLn(' blaise.cfg (next to the binary, then ~/.blaise.cfg) can set, one');
|
||||
WriteLn(' per line: unit-path=<dir> (repeatable) and rtl-src=<dir>. A relative');
|
||||
WriteLn(' path is resolved against the config file''s directory; --rtl-src on');
|
||||
WriteLn(' the command line overrides rtl-src= in the config.');
|
||||
end;
|
||||
|
||||
{ Populate the two caller-constructed opts objects from the command line.
|
||||
|
|
@ -147,11 +154,29 @@ end;
|
|||
bad flag; the caller owns and frees both objects regardless. }
|
||||
{ Apply each -d/--define symbol in ADefines to ALexer's conditional-compilation
|
||||
table. No-op when ADefines is nil/empty. }
|
||||
{ True if ASym is one of the OS conditional-compilation symbols. }
|
||||
function IsOSDefine(const ASym: string): Boolean;
|
||||
var U: string;
|
||||
begin
|
||||
U := UpperCase(ASym);
|
||||
Result := (U = 'LINUX') or (U = 'FREEBSD') or (U = 'WINDOWS')
|
||||
or (U = 'DARWIN') or (U = 'UNIX');
|
||||
end;
|
||||
|
||||
procedure AddDefinesTo(ALexer: TLexer; ADefines: TStringList);
|
||||
var
|
||||
I: Integer;
|
||||
HasOS: Boolean;
|
||||
begin
|
||||
if ADefines = nil then Exit;
|
||||
{ If the caller supplies an OS symbol (a cross --target injects the target's),
|
||||
it REPLACES the host OS symbols the lexer seeded in SeedPredefines: drop
|
||||
those first so an IFDEF LINUX etc. reflects the target, not the host. }
|
||||
HasOS := False;
|
||||
for I := 0 to ADefines.Count - 1 do
|
||||
if IsOSDefine(ADefines.Strings[I]) then HasOS := True;
|
||||
if HasOS then
|
||||
ALexer.ClearOSDefines();
|
||||
for I := 0 to ADefines.Count - 1 do
|
||||
ALexer.AddDefine(ADefines.Strings[I]);
|
||||
end;
|
||||
|
|
@ -444,6 +469,7 @@ var
|
|||
SourceFile, OutputFile: string;
|
||||
SearchPaths: TStringList;
|
||||
ConfigPaths: TStringList;
|
||||
CfgRtlSrc: string;
|
||||
EmitIR: Boolean;
|
||||
EmitAsm: Boolean;
|
||||
DumpAST: Boolean;
|
||||
|
|
@ -531,6 +557,23 @@ begin
|
|||
Halt(1);
|
||||
end;
|
||||
|
||||
{ The OS conditional-compilation symbols follow the resolved --target (which
|
||||
defaults to the host). Inject them into Front.Defines; AddDefinesTo then
|
||||
replaces the lexer's host-seeded OS symbols with these on every lexer (top
|
||||
program and, via the unit loader, each dependency unit). So an IFDEF
|
||||
FREEBSD in compiled source reflects the TARGET, not this compiler's host,
|
||||
which is what makes cross-compiling blaise itself for FreeBSD bake
|
||||
HostTarget=FreeBSD into the result. }
|
||||
case Opts.Target.OS of
|
||||
osFreeBSD: Front.Defines.Add('FREEBSD');
|
||||
osWindows: Front.Defines.Add('WINDOWS');
|
||||
osMacOS: Front.Defines.Add('DARWIN');
|
||||
else
|
||||
Front.Defines.Add('LINUX');
|
||||
end;
|
||||
if Opts.Target.OS <> osWindows then
|
||||
Front.Defines.Add('UNIX');
|
||||
|
||||
{ Seed the working locals from the opts objects. Keeping the locals lets
|
||||
the (large) main body read them unchanged; the parser owns the objects.
|
||||
SearchPaths is the object's owned list (not copied) so config-path
|
||||
|
|
@ -556,7 +599,14 @@ begin
|
|||
|
||||
ConfigPaths := TStringList.Create();
|
||||
try
|
||||
LoadConfigPaths(ConfigPaths);
|
||||
{ blaise.cfg supplies extra unit-paths and, optionally, rtl-src. A CLI
|
||||
--rtl-src (already in Opts.RTLSrcDir) takes precedence: only let the config
|
||||
set it when the CLI did not. Seed CfgRtlSrc with the CLI value so the
|
||||
config only overrides an EMPTY one. }
|
||||
CfgRtlSrc := Opts.RTLSrcDir;
|
||||
LoadConfigPaths(ConfigPaths, CfgRtlSrc);
|
||||
if Opts.RTLSrcDir = '' then
|
||||
Opts.RTLSrcDir := CfgRtlSrc;
|
||||
for I := ConfigPaths.Count - 1 downto 0 do
|
||||
SearchPaths.Insert(0, ConfigPaths.Strings[I]);
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -107,14 +107,22 @@ end;
|
|||
|
||||
function HostTarget: TTargetDesc;
|
||||
begin
|
||||
{ A '.exe' suffix on our own path means we are running on Windows (incl.
|
||||
under wine). Unix hosts carry no extension; we can't cheaply tell
|
||||
linux/macos/freebsd apart here, and only the Windows distinction matters
|
||||
for tool extensions today, so default the unix case to linux. }
|
||||
if LowerCase(ExtractFileExt(ParamStr(0))) = '.exe' then
|
||||
MakeTarget(osWindows, cpuX86_64, Result)
|
||||
else
|
||||
MakeTarget(osLinux, cpuX86_64, Result);
|
||||
{ The host OS is a COMPILE-TIME property of this binary: a blaise built with
|
||||
`--target freebsd-x86_64` runs on FreeBSD, so its HostTarget must report
|
||||
FreeBSD. The OS conditional symbols are target-driven (injected by the
|
||||
driver from the resolved --target; see Blaise.pas and uLexer.SeedPredefines),
|
||||
the IFDEF FREEBSD below is true exactly when THIS compiler was built for
|
||||
FreeBSD. This is what lets a FreeBSD-hosted blaise default (no --target) to
|
||||
producing FreeBSD binaries — the basis of self-hosting on FreeBSD. }
|
||||
{$IFDEF FREEBSD}
|
||||
MakeTarget(osFreeBSD, cpuX86_64, Result);
|
||||
{$ELSE}
|
||||
{$IFDEF WINDOWS}
|
||||
MakeTarget(osWindows, cpuX86_64, Result);
|
||||
{$ELSE}
|
||||
MakeTarget(osLinux, cpuX86_64, Result);
|
||||
{$ENDIF}
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
function PtrSize(const ATarget: TTargetDesc): Integer;
|
||||
|
|
|
|||
|
|
@ -267,7 +267,18 @@ asm
|
|||
jmp main
|
||||
end;
|
||||
|
||||
{ The C-level entry: SP points at the kernel's initial stack (argc at [SP]). }
|
||||
|
||||
|
||||
{ The C-level entry. The kernel hands us the initial process stack: argc, then
|
||||
argv[], NULL, envp[], NULL, auxv. On FreeBSD the entry %rsp must be 16-byte
|
||||
aligned, and the kernel achieves that by inserting a ZERO PAD WORD immediately
|
||||
below argc when the argument-vector layout would otherwise leave argc at an
|
||||
8-mod-16 address (this depends on the argc+envc pointer-count parity, so it
|
||||
varies between the shell-launched top-level and an execve'd child with a
|
||||
different environment). So argc is at [SP] usually, but at [SP+8] when a pad
|
||||
word is present. Detect the pad: a real argc is a small non-negative count
|
||||
and the slot after it (argv[0]) is a valid pointer; a pad word reads 0. If
|
||||
[SP] is 0 and [SP+8] is a plausible argc, skip the pad word. }
|
||||
procedure _BlaiseStartC(SP: Pointer);
|
||||
var
|
||||
Argc: Int64;
|
||||
|
|
@ -275,6 +286,13 @@ var
|
|||
Ret: Integer;
|
||||
begin
|
||||
Argc := PInt64(SP)^;
|
||||
{ Skip a kernel alignment pad word below argc, if present. }
|
||||
if Argc = 0 then
|
||||
begin
|
||||
if PInt64(Pointer(PChar(SP) + 8))^ > 0 then
|
||||
SP := Pointer(PChar(SP) + 8);
|
||||
Argc := PInt64(SP)^;
|
||||
end;
|
||||
Argv := Pointer(PChar(SP) + 8);
|
||||
{ envp = &argv[argc+1] }
|
||||
Envp := Pointer(PChar(Argv) + (Argc + 1) * 8);
|
||||
|
|
|
|||
|
|
@ -457,6 +457,14 @@ asm
|
|||
ret
|
||||
end;
|
||||
|
||||
{ FreeBSD fork(2) has a two-register return the libc wrapper normalises and a
|
||||
raw syscall must replicate: the kernel returns the child pid in %rax for the
|
||||
PARENT with %edx = 0, but for the CHILD it returns the PARENT's pid in %rax
|
||||
with %edx = 1 as the child indicator. Without zeroing the child's %eax when
|
||||
%edx != 0, the child sees a non-zero "pid" and every `if fork() = 0 then
|
||||
(child path)` check runs the PARENT branch in the child — so a spawned
|
||||
subprocess never execve's its target and the parent reaps exit 127. Mirror
|
||||
libc: if %edx != 0, this is the child -> return 0. }
|
||||
function fork: Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
|
|
@ -464,7 +472,12 @@ asm
|
|||
syscall
|
||||
jae .Lok_fork
|
||||
negq %rax
|
||||
ret
|
||||
.Lok_fork:
|
||||
testl %edx, %edx { %edx = 1 in the child, 0 in the parent }
|
||||
jz .Lparent_fork
|
||||
xorl %eax, %eax { child: return 0 }
|
||||
.Lparent_fork:
|
||||
ret
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -209,10 +209,15 @@ begin
|
|||
CB := PThreadCB(CBPtr);
|
||||
Entry := CB^.StartRoutine;
|
||||
CallEntry(Entry, CB^.Arg);
|
||||
{ Signal completion to any joiner, then terminate this thread only. }
|
||||
CB^.JoinWord := 1;
|
||||
_umtx_op(@CB^.JoinWord, UMTX_OP_WAKE_PRIVATE, 1, nil, nil);
|
||||
thr_exit(nil);
|
||||
{ Terminate this thread AND signal the joiner in one kernel step: pass the
|
||||
join word as thr_exit's `state`. The kernel does `suword(state, 1)` +
|
||||
umtx-wake on it AFTER the thread has stopped executing on its stack — so the
|
||||
joiner cannot free the stack while this thread is still running on it. (The
|
||||
previous code set JoinWord + WAKEd here, on the stack, BEFORE thr_exit, which
|
||||
let pthread_join munmap the shared stack/CB region out from under this still-
|
||||
live thread → SIGSEGV during teardown. Never touch the stack after the wake;
|
||||
let thr_exit's kernel-side wake be the last event on this word.) }
|
||||
thr_exit(@CB^.JoinWord);
|
||||
end;
|
||||
|
||||
{ Store an 8-byte value at Base + Offset (used to fill struct thr_param). }
|
||||
|
|
|
|||
|
|
@ -15,10 +15,22 @@ uses
|
|||
|
||||
function FindConfigFile: string;
|
||||
|
||||
{ Parse blaise.cfg lines. Recognised keys (KEY=VALUE, '#' comments, blank lines
|
||||
ignored): `unit-path=<dir>` appends to APaths; `rtl-src=<dir>` sets ARtlSrc.
|
||||
A relative VALUE is resolved against ABaseDir (the config file's directory).
|
||||
ARtlSrc is only written when an rtl-src line is present (left unchanged
|
||||
otherwise), so a caller can pre-seed it and let the config override only if
|
||||
set. }
|
||||
procedure ParseConfigLines(ALines: TStringList; const ABaseDir: string;
|
||||
APaths: TStringList);
|
||||
APaths: TStringList; var ARtlSrc: string); overload;
|
||||
|
||||
procedure LoadConfigPaths(APaths: TStringList);
|
||||
{ Convenience overload for callers that only want unit-paths (ignores rtl-src). }
|
||||
procedure ParseConfigLines(ALines: TStringList; const ABaseDir: string;
|
||||
APaths: TStringList); overload;
|
||||
|
||||
{ Load the resolved blaise.cfg: appends unit-path entries to APaths and returns
|
||||
any rtl-src via ARtlSrc (unchanged if the file has none / no file). }
|
||||
procedure LoadConfigPaths(APaths: TStringList; var ARtlSrc: string);
|
||||
|
||||
implementation
|
||||
|
||||
|
|
@ -42,7 +54,7 @@ begin
|
|||
end;
|
||||
|
||||
procedure ParseConfigLines(ALines: TStringList; const ABaseDir: string;
|
||||
APaths: TStringList);
|
||||
APaths: TStringList; var ARtlSrc: string);
|
||||
var
|
||||
I: Integer;
|
||||
Line: string;
|
||||
|
|
@ -68,12 +80,27 @@ begin
|
|||
Value := IncludeTrailingPathDelimiter(ABaseDir) + Value;
|
||||
APaths.Add(Value);
|
||||
end
|
||||
else if SameText(Key, 'rtl-src') then
|
||||
begin
|
||||
if (Length(Value) > 0) and (Copy(Value, 0, 1) <> '/') then
|
||||
Value := IncludeTrailingPathDelimiter(ABaseDir) + Value;
|
||||
ARtlSrc := Value;
|
||||
end
|
||||
else
|
||||
; { silently skip unrecognised keys }
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure LoadConfigPaths(APaths: TStringList);
|
||||
procedure ParseConfigLines(ALines: TStringList; const ABaseDir: string;
|
||||
APaths: TStringList);
|
||||
var
|
||||
IgnoredRtlSrc: string;
|
||||
begin
|
||||
IgnoredRtlSrc := '';
|
||||
ParseConfigLines(ALines, ABaseDir, APaths, IgnoredRtlSrc);
|
||||
end;
|
||||
|
||||
procedure LoadConfigPaths(APaths: TStringList; var ARtlSrc: string);
|
||||
var
|
||||
CfgFile: string;
|
||||
Lines: TStringList;
|
||||
|
|
@ -86,7 +113,7 @@ begin
|
|||
try
|
||||
Lines.LoadFromFile(CfgFile);
|
||||
BaseDir := ExtractFilePath(CfgFile);
|
||||
ParseConfigLines(Lines, BaseDir, APaths);
|
||||
ParseConfigLines(Lines, BaseDir, APaths, ARtlSrc);
|
||||
finally
|
||||
Lines.Free();
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ type
|
|||
{ Define a conditional-compilation symbol before lexing (e.g. from the
|
||||
-d command-line flag). Case-insensitive. }
|
||||
procedure AddDefine(const ASym: string);
|
||||
{ Drop the host OS predefines so a cross-target's OS symbol replaces them. }
|
||||
procedure ClearOSDefines;
|
||||
function Next: TToken;
|
||||
end;
|
||||
|
||||
|
|
@ -187,15 +189,29 @@ end;
|
|||
|
||||
{ Seed the always-on predefined conditional symbols. BLAISE identifies this
|
||||
compiler (the headline use case: cross-compiler IFDEF BLAISE / ELSE / ENDIF);
|
||||
the CPU/OS symbols mirror the FPC family so portable source compiles
|
||||
unchanged. Targets beyond Linux/x86-64 will extend this set. }
|
||||
the CPU/OS symbols mirror the FPC family so portable source compiles unchanged.
|
||||
The OS symbols follow the HOST this compiler was built for, via compile-time
|
||||
conditional compilation, so a FreeBSD-built blaise seeds FREEBSD (matching
|
||||
HostTarget), which is what makes it default to producing FreeBSD binaries.
|
||||
A cross-target does not re-seed these; source that switches on the target OS
|
||||
is rare and the RTL selects per-target units structurally (BuildRTLUnitList),
|
||||
not via conditional compilation. }
|
||||
procedure TLexer.SeedPredefines;
|
||||
begin
|
||||
Self.DefineSymbol('BLAISE');
|
||||
Self.DefineSymbol('CPUX86_64');
|
||||
Self.DefineSymbol('CPUAMD64'); { FPC's alias for the same target }
|
||||
{$IFDEF FREEBSD}
|
||||
Self.DefineSymbol('FREEBSD');
|
||||
Self.DefineSymbol('UNIX');
|
||||
{$ELSE}
|
||||
{$IFDEF WINDOWS}
|
||||
Self.DefineSymbol('WINDOWS');
|
||||
{$ELSE}
|
||||
Self.DefineSymbol('LINUX');
|
||||
Self.DefineSymbol('UNIX');
|
||||
{$ENDIF}
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
function TLexer.IsDefined(const ASym: string): Boolean;
|
||||
|
|
@ -218,6 +234,17 @@ begin
|
|||
FDefines.Delete(Idx);
|
||||
end;
|
||||
|
||||
{ Remove all OS predefines seeded for the host, so a cross --target's OS symbol
|
||||
can take their place (the driver calls this before applying target defines). }
|
||||
procedure TLexer.ClearOSDefines;
|
||||
begin
|
||||
Self.UndefSymbol('LINUX');
|
||||
Self.UndefSymbol('FREEBSD');
|
||||
Self.UndefSymbol('WINDOWS');
|
||||
Self.UndefSymbol('DARWIN');
|
||||
Self.UndefSymbol('UNIX');
|
||||
end;
|
||||
|
||||
procedure TLexer.AddDefine(const ASym: string);
|
||||
begin
|
||||
Self.DefineSymbol(UpperCase(ASym));
|
||||
|
|
|
|||
|
|
@ -103,6 +103,15 @@ type
|
|||
|
||||
implementation
|
||||
|
||||
{ True if ASym is one of the OS conditional-compilation symbols (the target's
|
||||
OS define, injected by the driver, replaces the lexer's host-seeded ones). }
|
||||
function DefineIsOS(const ASym: string): Boolean;
|
||||
begin
|
||||
Result := SameText(ASym, 'LINUX') or SameText(ASym, 'FREEBSD') or
|
||||
SameText(ASym, 'WINDOWS') or SameText(ASym, 'DARWIN') or
|
||||
SameText(ASym, 'UNIX');
|
||||
end;
|
||||
|
||||
function TUnitLoader.IsBuiltin(const AName: string): Boolean;
|
||||
begin
|
||||
Result :=
|
||||
|
|
@ -240,16 +249,27 @@ var
|
|||
L: TLexer;
|
||||
P: TParser;
|
||||
DI: Integer;
|
||||
HasOS: Boolean;
|
||||
begin
|
||||
SL := TStringList.Create();
|
||||
try
|
||||
SL.LoadFromFile(APath);
|
||||
L := TLexer.Create(SL.Text, APath);
|
||||
{ Apply the same -d/--define symbols the main program got, so IFDEF
|
||||
directives resolve consistently across the program and all its units. }
|
||||
directives resolve consistently across the program and all its units.
|
||||
If the set carries an OS symbol (the target's, injected by the driver),
|
||||
drop the lexer's host-seeded OS symbols first so the target wins here too
|
||||
— mirrors AddDefinesTo in Blaise.pas. }
|
||||
if FDefines <> nil then
|
||||
begin
|
||||
HasOS := False;
|
||||
for DI := 0 to FDefines.Count - 1 do
|
||||
if DefineIsOS(FDefines.Strings[DI]) then HasOS := True;
|
||||
if HasOS then
|
||||
L.ClearOSDefines();
|
||||
for DI := 0 to FDefines.Count - 1 do
|
||||
L.AddDefine(FDefines.Strings[DI]);
|
||||
end;
|
||||
try
|
||||
P := TParser.Create(L);
|
||||
try
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ type
|
|||
procedure TestParseRelativePath_ResolvedAgainstBaseDir;
|
||||
procedure TestParseAbsolutePath_NotModified;
|
||||
procedure TestParseUnknownKey_IgnoredSilently;
|
||||
procedure TestParseRtlSrc_Absolute_SetsValue;
|
||||
procedure TestParseRtlSrc_Relative_ResolvedAgainstBaseDir;
|
||||
procedure TestParseRtlSrc_Absent_LeavesSeedUnchanged;
|
||||
procedure TestFindConfigFile_NextToBinary;
|
||||
end;
|
||||
|
||||
|
|
@ -227,6 +230,65 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TConfigTests.TestParseRtlSrc_Absolute_SetsValue;
|
||||
var
|
||||
Lines, Paths: TStringList;
|
||||
Rtl: string;
|
||||
begin
|
||||
Lines := TStringList.Create();
|
||||
Paths := TStringList.Create();
|
||||
try
|
||||
Rtl := '';
|
||||
Lines.Add('rtl-src=/opt/blaise/rtl');
|
||||
ParseConfigLines(Lines, '/base', Paths, Rtl);
|
||||
AssertEquals('rtl-src set', '/opt/blaise/rtl', Rtl);
|
||||
AssertEquals('rtl-src is not a unit-path', 0, Paths.Count);
|
||||
finally
|
||||
Lines.Free();
|
||||
Paths.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TConfigTests.TestParseRtlSrc_Relative_ResolvedAgainstBaseDir;
|
||||
var
|
||||
Lines, Paths: TStringList;
|
||||
Rtl: string;
|
||||
begin
|
||||
Lines := TStringList.Create();
|
||||
Paths := TStringList.Create();
|
||||
try
|
||||
Rtl := '';
|
||||
Lines.Add('rtl-src=src/main/pascal');
|
||||
ParseConfigLines(Lines, '/base/', Paths, Rtl);
|
||||
AssertEquals('relative rtl-src resolved against base dir',
|
||||
'/base/src/main/pascal', Rtl);
|
||||
finally
|
||||
Lines.Free();
|
||||
Paths.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TConfigTests.TestParseRtlSrc_Absent_LeavesSeedUnchanged;
|
||||
var
|
||||
Lines, Paths: TStringList;
|
||||
Rtl: string;
|
||||
begin
|
||||
Lines := TStringList.Create();
|
||||
Paths := TStringList.Create();
|
||||
try
|
||||
{ Seed a value (as the driver does with a CLI --rtl-src); a config with no
|
||||
rtl-src line must leave it untouched. }
|
||||
Rtl := '/cli/override';
|
||||
Lines.Add('unit-path=/opt/blaise/runtime');
|
||||
ParseConfigLines(Lines, '/base', Paths, Rtl);
|
||||
AssertEquals('seed unchanged when config has no rtl-src',
|
||||
'/cli/override', Rtl);
|
||||
finally
|
||||
Lines.Free();
|
||||
Paths.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TConfigTests.TestFindConfigFile_NextToBinary;
|
||||
var
|
||||
CfgPath: string;
|
||||
|
|
|
|||
Loading…
Reference in a new issue