fix(ninja): link shared libraries through the link_shared rule - #86
fix(ninja): link shared libraries through the link_shared rule#86AshrafAhmed9 wants to merge 1 commit into
Conversation
Shared library support landed twice, in two incompatible shapes, and the halves were never reconciled. The generator defines a link_shared rule, but nothing ever refers to it: the build statement for a shared_library uses the plain link rule and pushes -shared (or -dynamiclib) into that target's ldflags instead. So link_shared is dead, and because it is emitted unconditionally its hardcoded -shared lands in the build.ninja of projects that build nothing but static archives. Each half also brought its own test, and they contradict each other. tests/ebuild asserts the shared library is built by link_shared; tests/unit asserts it is built by link. Both cannot hold, and on master three tests fail on every platform in the CI matrix. Use the rule. It is the one that produces a correct LINK_SHARED line in ninja's output, and putting the flag in the rule keeps it out of every static target's ldflags. Emit it only when a shared_library target exists, so a static-only project's build.ninja does not carry a rule for a link it never performs, and give it the platform's spelling rather than a hardcoded -shared, since the matrix includes macos-13. Two test assertions move with it. The tests/unit one asserted ": link ", whose stated purpose in its own comment is that the target goes through a compiler driver rather than ar_rule; that now checks exactly that. The tests/ebuild one hardcoded -shared and would have failed on the macOS runner once the rule was reached at all. The suite goes from 3 failures to 201 passed, 1 skipped.
46710db to
8541f29
Compare
srpatcha
left a comment
There was a problem hiding this comment.
You found a real bug, and independently of the person who found it first. Worth
saying that up front, because the conclusion below is that this should probably
close.
The defect you are fixing is genuine: the link_shared rule hardcoded
command = $cc -shared $ldflags $in -o $out $libs
and -shared is not the flag on Darwin. -dynamiclib is correct.
The conflict is a design disagreement, not a merge artifact
#66 fixes the same bug by the opposite route. Rather than correcting the rule's
flag, it deletes the rule, on the grounds that nothing used it:
the emitted
link_sharedrule was dead — nothing used it — and it hardcoded
-shared, which is wrong on macOS. Shared libraries already link through the
genericlinkrule with the platform's flag (-dynamiclib/-shared) and the
-L/-lwiring in ldflags.
Its test asserts that outcome directly:
assert ": link " in lib_line
assert f"ldflags = {shared_flag}" in ninja_file
assert "link_shared" not in ninja_fileSo the two PRs cannot both land. This one reintroduces a rule #66 removed
on purpose, and #66's suite fails the moment it comes back.
Why I would keep #66's version
Both spell the flag correctly per platform, so that is a wash. The difference is
structural:
- #66 has one link rule; the platform flag rides in per-target
ldflags
alongside the-L/-lwiring that shared libraries need anyway. - This has two rules, plus a conditional emission to avoid writing a rule a
static-only project never uses.
The conditional emission is careful work — I like that it keeps build.ninja
free of a rule for a link that never happens — but it exists to manage a rule
that does not have to exist. And #66 notes the removal also unblocks
test_static_library_unaffected, which asserts no -shared appears anywhere in
the generated file; a conditionally-emitted rule satisfies that only for as long
as the condition holds.
#66 is also already approved and carries the dispatch.py syntax repair that
master needs (#87), so it will land first regardless.
What I would do
Close this in favour of #66 — but not silently. Two people finding the same
macOS flag bug independently is worth a line in the changelog, and if you want
the fix attributed, say so on #66 and I will make sure it is.
If you would rather keep contributing here, the ninja backend still has open
work: #79 and #80 are both in this file and both conflict with #66 as well.
For the record, verified on #66's branch:
pytest tests/ -k "shared or ninja" 15 passed, 1 skipped
so the surviving path is covered rather than merely asserted-away.
|
Agreed, closing in favour of #66. The structural argument is the right one. One link rule with the flag riding in per-target Taking you up on the changelog line, thanks for offering rather than just closing it. I'll have a look at #79 and #80. |
Shared library support landed twice, in two shapes that don't agree, and the
halves were never reconciled.
_write_ninja()defines alink_sharedrule:Nothing refers to it. The build statement for a
shared_librarytarget uses theplain
linkrule and pushes the platform flag into that target'sldflagsinstead:
So
link_sharedis dead code, and since it's emitted unconditionally itshardcoded
-sharedshows up in thebuild.ninjaof a project that buildsnothing but static archives.
Each half also brought its own test.
tests/ebuild/...::test_shared_library_uses_shared_link_ruleexpects
: link_shared,tests/unit/...::test_shared_library_gets_shared_flagexpects
: link, andtest_static_library_unaffectedexpects no-sharedanywhere. On master all three fail, on every entry in the CI matrix:
I went with the rule rather than deleting it. It produces a correct
LINK_SHAREDline in ninja's output instead of labelling a shared linkLINK,and holding the flag in the rule keeps it out of the ldflags of every target
that isn't a shared library. Two details the old rule got wrong, fixed on the
way: it's now emitted only when a
shared_librarytarget exists, and the flagis the platform's spelling rather than a hardcoded
-shared, since the matrixincludes
macos-13where it's-dynamiclib.Two test assertions move with it.
tests/unitasserted": link "; the commentabove it says its purpose is that the target goes through a compiler driver
rather than
ar_rule, so it now asserts exactly that.tests/ebuildhardcoded-shared, which would have failed on the macOS runner as soon as the rule wasreached at all.
Testing
macOS 15, Python 3.14.
Reverting
ninja_backend.pyalone and keeping the tests reproduces the threefailures above, so the tests do exercise the change.
ldflagsfor a sharedtarget now contains only what the target and its packages asked for;
-L/-lwiring is unchanged, and
test_shared_library_gets_lib_dirs_and_libsstillcovers it.
One caveat on the numbers: master doesn't import right now —
ebuild/build/dispatch.pyhas a
SyntaxErrorandNinjaBackend._object_pathis missing, which #78/#75and #79 address. I ran the suite with those two applied locally to get a
baseline. This branch touches neither file, so it should merge in any order
relative to them.