You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
runtime.library_dirs also lands on the link line, where it can shadow another package's library #304
A package's runtime.library_dirs is also emitted as -L on the link line. The name
says runtime, and the field exists to make dlopen work, but a directory listed there
participates in symbol resolution at build time too — where it can shadow another
package's library and break the link.
Not asking for a behaviour change; the current semantics may well be intended. Asking for
it to be documented, because the failure is remote from its cause and the workaround is
non-obvious.
Where it came up
compat.vulkan-runtime (mcpp-index) is a symlink farm of host Vulkan ICD libraries, in
the shape compat.glx-runtime already uses for OpenGL: an mcpp binary runs under mcpp's
own glibc, so a bare-soname dlopen never searches the host's library path, and the
loader finds every ICD manifest and then fails to open a single driver.
The farm has to carry the ICDs' transitive dependencies, and some of those — libxcb, zlib
— are also packaged in the index. Harvesting the host's libxcb.so (the unversioned
development symlink) put it on -L ahead of compat.xcb, and the link failed:
ld: …/vulkan_runtime/lib/libxcb.so: undefined reference to `XauDisposeAuth'
ld: …/vulkan_runtime/lib/libxcb.so: undefined reference to `XauGetBestAuthByAddr'
ld: …/vulkan_runtime/lib/libxcb.so: undefined reference to `XdmcpWrap'
Nothing in that message points at runtime.library_dirs.
Reproduction
A package declaring runtime = { library_dirs = { "<dir>" } }, where <dir> contains
an unversioned libfoo.so for some foo the index also provides.
A consumer that links -lfoo through the index package.
mcpp build → the host copy wins and the link fails.
Confirmed on 0.0.109 by inspecting the generated build.ninja: the directory appears as
a plain -L… entry.
Workaround, which is arguably the right spelling anyway
Harvest only versioned sonames (lib*.so.*), never the bare .so. Versioned names
are invisible to the linker — it resolves -lfoo through libfoo.so/libfoo.a — and are
exactly what dlopen asks for. compat.glx-runtime never hit this because the GL family
it harvests is not otherwise linked from the index, so the collision never arose.
Suggested resolution
Either of these would have saved the debugging:
Document that runtime.library_dirs contributes to the link search path, with the
"versioned sonames only" guidance for symlink-farm packages; or
Keep it out of the link line, if runtime-only was the intent.
Happy to send a docs patch if the first is the preferred direction.
Fixed in 2026.8.10.1 (PR #400) — closing late, recording the evidence.
The issue asked only that the behaviour be documented. It turned out not to be a trade-off at all, so the behaviour changed instead.
The sweep that triaged this issue found a third option neither the report nor the response had considered: -Wl,-rpath-link satisfies link-time resolution of a transitive DT_NEEDED and does not participate in -lfoo resolution (measured identically on binutils 2.42 and ld.lld 22.1.8). "Keep the link-time capability" and "do not shadow another package's library" are therefore not in tension — the old behaviour was one flag spelled wrong.
Link intent now keeps the discovery stages separate, one field per stage:
Field
ELF
Mach-O
PE/Windows
link_library_dirs
-L
-L
-L or /LIBPATH:
transitive_needed_dirs
-Wl,-rpath-link
no flag
no flag
runtime_search_dirs
RUNPATH/rpath only, never -L
rpath only
no flag
frameworks
no flag
-framework
no flag
deploy_files
copy edge
copy edge
copy beside the output
Legacy runtime.library_dirs maps to run-time search only. src/build/plan.cppm:703:
// Legacy library_dirs means run-time discovery only. It deliberately// does not enter linkLibraryDirs; callers that need -L must opt into// the structured field.append_paths(runtime.libraryDirs, out.linkIntent.runtimeSearchDirs);
So the compat.vulkan-runtime shape works as intended: a symlink farm of host ICDs and their transitive dependencies is reachable at run time, its harvested libxcb.so no longer precedes compat.xcb on the link line, and a package that genuinely needs -L says so with link_library_dirs.
Documented in docs/05-mcpp-toml.md and docs/zh/05-mcpp-toml.md (the table above), which also covers what the issue originally asked for: the failure was remote from its cause, and the field names now state the stage each one belongs to.
Activity
speak-agent commentedon Aug 31, 2026
Fixed in 2026.8.10.1 (PR #400) — closing late, recording the evidence.
The issue asked only that the behaviour be documented. It turned out not to be a trade-off at all, so the behaviour changed instead.
The sweep that triaged this issue found a third option neither the report nor the response had considered:
-Wl,-rpath-linksatisfies link-time resolution of a transitiveDT_NEEDEDand does not participate in-lfooresolution (measured identically on binutils 2.42 and ld.lld 22.1.8). "Keep the link-time capability" and "do not shadow another package's library" are therefore not in tension — the old behaviour was one flag spelled wrong.Link intent now keeps the discovery stages separate, one field per stage:
link_library_dirs-L-L-Lor/LIBPATH:transitive_needed_dirs-Wl,-rpath-linkruntime_search_dirs-Lframeworks-frameworkdeploy_filesLegacy
runtime.library_dirsmaps to run-time search only.src/build/plan.cppm:703:So the
compat.vulkan-runtimeshape works as intended: a symlink farm of host ICDs and their transitive dependencies is reachable at run time, its harvestedlibxcb.sono longer precedescompat.xcbon the link line, and a package that genuinely needs-Lsays so withlink_library_dirs.Documented in
docs/05-mcpp-toml.mdanddocs/zh/05-mcpp-toml.md(the table above), which also covers what the issue originally asked for: the failure was remote from its cause, and the field names now state the stage each one belongs to.