feat(link): pass external-library deps to the linker as -l<name>
The libraries collected from `external 'lib'` declarations (the program's
and every used unit's TUnitInterface.LinkLibs) are unioned into
TBackendOpts.LinkLibs and emitted on the toolchain link line as one
-l<name> each, additive to the always-needed -lm/-lpthread. Both the QBE
and native (external-linker) paths route through LinkViaToolchain and so
gain the flags.
The in-process internal linker (--linker internal) links only Blaise ELF
objects + the source-built RTL and has no concept of -l<name> system
libraries. Rather than silently drop a declared dependency and emit a
binary with unresolved symbols, LinkViaInternalLinker now fails loudly
when LinkLibs is non-empty, pointing the user at --linker external.
Tests: TestExternalLib_MissingLib_FailsLink_{QBE,Native} drive the real
compiler binary end to end (the e2e harness links with a hardcoded
`cc ... -lm -lpthread` and never calls LinkViaToolchain, so it cannot
exercise this). A program declaring `external 'nosuchlib...'` — never
called, but collected into LinkLibs at parse time — must fail the link
with the linker's "cannot find -lnosuchlib...", which only happens if the
driver actually emitted the flag.
This commit is contained in:
parent
c1f14b5c78
commit
edf4ffe374
|
|
@ -502,6 +502,7 @@ var
|
|||
any ExportUnitInterface bugs against real
|
||||
codebases. }
|
||||
I: Integer;
|
||||
J: Integer;
|
||||
IR: string;
|
||||
IRFile: string;
|
||||
LinkErr: string; { Driver.LinkProgram result ('' on success) }
|
||||
|
|
@ -1055,6 +1056,22 @@ begin
|
|||
if PrebuiltObjPaths.IndexOf(Loader.LinkOnlyObjects.Strings[I]) < 0 then
|
||||
PrebuiltObjPaths.Add(Loader.LinkOnlyObjects.Strings[I]);
|
||||
end;
|
||||
{ Capture link-library deps (-l<name>) off the program AST and every used
|
||||
unit's interface before Prog/Loader are freed below — the link step runs
|
||||
after this finally block. Unioned into Opts.LinkLibs; the driver emits one
|
||||
-l<name> each, additive to the RTL's -lm/-lpthread. }
|
||||
if Opts.LinkLibs = nil then Opts.LinkLibs := TStringList.Create();
|
||||
if (Prog <> nil) and (Prog.LinkLibs <> nil) then
|
||||
for I := 0 to Prog.LinkLibs.Count - 1 do
|
||||
if Opts.LinkLibs.IndexOf(TLinkLibDecl(Prog.LinkLibs.Items[I]).LibName) < 0 then
|
||||
Opts.LinkLibs.Add(TLinkLibDecl(Prog.LinkLibs.Items[I]).LibName);
|
||||
if Loader <> nil then
|
||||
for I := 0 to Loader.PrebuiltIfaces.Count - 1 do
|
||||
for J := 0 to TUnitInterface(Loader.PrebuiltIfaces.Items[I]).LinkLibs.Count - 1 do
|
||||
if Opts.LinkLibs.IndexOf(
|
||||
TUnitInterface(Loader.PrebuiltIfaces.Items[I]).LinkLibs.Strings[J]) < 0 then
|
||||
Opts.LinkLibs.Add(
|
||||
TUnitInterface(Loader.PrebuiltIfaces.Items[I]).LinkLibs.Strings[J]);
|
||||
Units.Free();
|
||||
Loader.Free();
|
||||
SearchPaths.Free();
|
||||
|
|
|
|||
|
|
@ -98,6 +98,11 @@ type
|
|||
runtime.start.static.<os>) replaces libc; the
|
||||
linker emits a non-PIE ET_EXEC with no
|
||||
PT_INTERP. docs/linux-syscall-migration.adoc }
|
||||
LinkLibs: TStringList; { extra libraries to link (-l<name>), unioned by
|
||||
the frontend from the program's and its used
|
||||
units' 'external ''lib''' declarations
|
||||
(TUnitInterface.LinkLibs). nil = none. Owned;
|
||||
ARC-released with the opts object. }
|
||||
end;
|
||||
|
||||
TBackendDriver = class
|
||||
|
|
@ -631,6 +636,12 @@ begin
|
|||
Args.Add(RTLObjs.Strings[I]);
|
||||
Args.Add('-lm'); { math functions (sqrt, sin, cos, etc.) }
|
||||
Args.Add('-lpthread'); { POSIX threads (blaise_thread unit) }
|
||||
{ Libraries declared via 'external ''lib''' in the program or any used unit
|
||||
(unioned by the frontend into AOpts.LinkLibs). Emitted as -l<name> — ld
|
||||
expands to lib<name>.so/.a. Additive to the always-needed -lm/-lpthread. }
|
||||
if AOpts.LinkLibs <> nil then
|
||||
for I := 0 to AOpts.LinkLibs.Count - 1 do
|
||||
Args.Add('-l' + AOpts.LinkLibs.Strings[I]);
|
||||
ExitCode := RunProcess(Self.ToolPath('linker', AOpts.Target), Args, Msg);
|
||||
finally
|
||||
RTLObjs.Free();
|
||||
|
|
|
|||
|
|
@ -197,6 +197,16 @@ var
|
|||
begin
|
||||
Result := '';
|
||||
|
||||
{ The internal linker links only Blaise ELF objects (+ the source-built RTL);
|
||||
it has no concept of -l<name> system libraries. A program that declares an
|
||||
`external 'lib'` dependency therefore cannot be linked this way — fail loudly
|
||||
rather than silently drop the library and emit a binary with unresolved
|
||||
symbols. Use the external toolchain linker (the default) for such programs. }
|
||||
if (AOpts.LinkLibs <> nil) and (AOpts.LinkLibs.Count > 0) then
|
||||
Exit('internal linker cannot resolve external library ''' +
|
||||
AOpts.LinkLibs.Strings[0] +
|
||||
''' (from an ''external ''''lib'''''' declaration); use --linker external');
|
||||
|
||||
{ Build the linker with the resolved target's TLinkTarget so the emitted ELF
|
||||
carries the right EI_OSABI / machine / load base for AOpts.Target — without
|
||||
this the internal linker always produced a Linux-shaped ELF regardless of
|
||||
|
|
|
|||
|
|
@ -75,6 +75,14 @@ type
|
|||
procedure TestEmitAsm_WithExplicitQbeBackend_Rejected;
|
||||
procedure TestEmitIr_WithoutBackend_StillWorks;
|
||||
procedure TestEmitIr_WithExplicitQbeBackend_StillWorks;
|
||||
{ ---- external 'lib' propagates a -l<name> to the real link command ---- }
|
||||
{ A program declaring `external 'lib'` for a NON-EXISTENT library must fail
|
||||
the link with the linker's "cannot find -l<lib>" — which only happens if
|
||||
the driver actually emitted -l<lib>. The e2e harness can't test this (it
|
||||
links with a hardcoded `cc ... -lm -lpthread` and never calls
|
||||
LinkViaToolchain), so this drives the real compiler binary end to end. }
|
||||
procedure TestExternalLib_MissingLib_FailsLink_QBE;
|
||||
procedure TestExternalLib_MissingLib_FailsLink_Native;
|
||||
{ ---- div/mod by zero raises a catchable EDivByZero (needs stdlib) ---- }
|
||||
procedure TestDivByZeroCaught_QBE;
|
||||
procedure TestDivByZeroCaught_Native;
|
||||
|
|
@ -557,6 +565,52 @@ const
|
|||
' end' + LineEnding +
|
||||
'end.';
|
||||
|
||||
const
|
||||
{ Declares an external from a library that does not exist. The function is
|
||||
never called, but the parser collects the library into Prog.LinkLibs at
|
||||
parse time, so the driver must still emit -lnosuchlib_blaise_xyz on the link
|
||||
line — which the system linker then rejects. If the -l<name> propagation
|
||||
were broken (no flag emitted), the link would SUCCEED. }
|
||||
SrcMissingExternalLib =
|
||||
'program P;' + LineEnding +
|
||||
'function f(X: Integer): Integer;' +
|
||||
' external ''nosuchlib_blaise_xyz'' name ''nope'';' + LineEnding +
|
||||
'begin' + LineEnding +
|
||||
' WriteLn(''unreached'')' + LineEnding +
|
||||
'end.';
|
||||
|
||||
procedure TCLIContractTests.TestExternalLib_MissingLib_FailsLink_QBE;
|
||||
var SrcPath, BinPath, Out_: string; EC: Integer;
|
||||
begin
|
||||
if not CompilerAvailable() then begin Ignore('<toolchain-missing>'); Exit; end;
|
||||
SrcPath := WriteScratchSource(SrcMissingExternalLib);
|
||||
BinPath := FScratch + 'cli_misslib_qbe_' + IntToStr(FCounter);
|
||||
EC := RunCompiler(['--source', SrcPath, '--backend', 'qbe',
|
||||
'--unit-path', FRTLPath, '--unit-path', FStdlibPath,
|
||||
'--output', BinPath], Out_);
|
||||
AssertTrue('link must fail (missing library)', EC <> 0);
|
||||
AssertTrue('linker reports the -l<name> it could not find',
|
||||
Pos('nosuchlib_blaise_xyz', Out_) >= 0);
|
||||
end;
|
||||
|
||||
procedure TCLIContractTests.TestExternalLib_MissingLib_FailsLink_Native;
|
||||
var SrcPath, BinPath, Out_: string; EC: Integer;
|
||||
begin
|
||||
if not CompilerAvailable() then begin Ignore('<toolchain-missing>'); Exit; end;
|
||||
SrcPath := WriteScratchSource(SrcMissingExternalLib);
|
||||
BinPath := FScratch + 'cli_misslib_nat_' + IntToStr(FCounter);
|
||||
{ --linker external: the internal linker cannot resolve -l<name> system
|
||||
libraries (it errors out separately); the toolchain linker is what emits
|
||||
and consumes -l<name>. }
|
||||
EC := RunCompiler(['--source', SrcPath, '--backend', 'native',
|
||||
'--linker', 'external',
|
||||
'--unit-path', FRTLPath, '--unit-path', FStdlibPath,
|
||||
'--output', BinPath], Out_);
|
||||
AssertTrue('link must fail (missing library)', EC <> 0);
|
||||
AssertTrue('linker reports the -l<name> it could not find',
|
||||
Pos('nosuchlib_blaise_xyz', Out_) >= 0);
|
||||
end;
|
||||
|
||||
procedure TCLIContractTests.TestDivByZeroCaught_QBE;
|
||||
var Out_: string; EC: Integer;
|
||||
begin
|
||||
|
|
|
|||
Loading…
Reference in a new issue