For context, I have been working on a game engine for a while now. The engine requires several libraries to function (Wayland, XKB, Vulkan, etc.). Because the added complexity of shared libraries leaves a bad taste in my mouth, the engine is archived into a static library via the ar utility, and I use Makefile to build the project.
I was trying to figure out an elegant way to link engine dependencies in games, and came across the ar -l option, which advertises to keep link-time dependencies alongside the library. However, when I specify said dependencies in the format they request (all in a string, in the format the linker expects), nothing is actually linked in the game executables, they still report the same missing function definitions.
Is this option unimplemented? Have I misread something?
What is the Purpose of the AR "-l" Option?
The behavior depends on what version of ar you're looking at.
POSIX, does not document such an option at all. Some implementations do not support it.
Some Unixes with a strong SysV lineage, such as AIX and IRIX, document it has having an effect on where temporary files are stored.
That SysV behavior is probably what inspired old versions of GNU ar to implement the l option as a do-nothing option for compatibility.
Your description sounds more like recent versions of GNU ar, which document l to have this effect:
Specify dependencies of this library. The dependencies must immediately follow this option character, must use the same syntax as the linker command line, and must be specified within a single argument. I.e., if multiple items are needed, they must be quoted to form a single command line argument. For example
l "-L/usr/local/lib -lmydep1 -lmydep2"
and that may be what inspired FreeBSD to implement -l as a do-nothing option for compatibility with GNU ar.
Even if you're using a recent version of GNU ar, it's not clear what it actually does with whatever data you provide via that option. Probably it stores that somewhere in the archive, but even that is uncertain. If it does store it in the archive then it's unclear where, as the format has no specific provision for it.
The purpose of this option is a different and more speculative question. The format expected for the data suggests that it might, under some circumstances, be used by the linker, but if so then probably only GNU ld and not any other. And that might well not be implemented in ld yet, and if ever it were then I would expect that to require an opt-in, as it could change the behavior of some builds. I don't find any such opt-in documented for ld, nor any reference at all to embedded dependency information.
Is this option unimplemented? Have I misread something?
At minimum, it is somewhat new, and very GNU-specific. Even if it does store the provided data somewhere in the archive, it is unclear whether any other tools do anything with that data. I guess you were hoping that the linker would automatically read that data and use it as additional link options, but if that's not working for you then that's probably end of story, at least for now. There does not appear to be any documented way to make the linker take notice.
I was trying to figure out an elegant way to link engine dependencies in games
Well there's an area where you get something for the added complexity of shared libraries that seems so distasteful to you. Shared libraries do carry information -- in a standard and well supported format -- about their dependencies.
Personally, I don't see or even really comprehend your particular objection, but if you insist on building your engine as a static library, and you want to provide a way for you or other developers to get at dependency information at build time, then you should consider an established, more widely recognized mechanism such as pkgconf.
The l option of GNU ar was implemented in binutils 2.36, Jan. 2021 and since the same release it interoperates with the GNU linker's static library dependencies plugin libdep.so (Windows, libdep.dll),
if enabled, to let the linker extract the dependency information of an input static library
from the library itself so that it need not be specified with additional linker arguments.
Per the GNU ar manual, binutils 2.44, the
effect of the l option is:
l Specify dependencies of this library. The dependencies must
immediately follow this option character, must use the same
syntax as the linker command line, and must be specified
within a single argument. I.e., if multiple items are needed,
they must be quoted to form a single command line argument.
For example l "-L/usr/local/lib -lmydep1 -lmydep2"
The long form of the option is --record-libdeps.
The interoperation of the libdep linker plugin and ar's --record-libdeps option is documented in the GNU ld manual, 4.1 Static Library Dependencies Plugin:
Originally, static libraries were contained in an archive file consisting just of a collection of relocatable object files. Later they evolved to optionally include a symbol table, to assist in finding the needed objects within a library. There their evolution ended, and dynamic libraries rose to ascendance.
One useful feature of dynamic libraries was that, more than just collecting multiple objects into a single file, they also included a list of their dependencies, such that one could specify just the name of a single dynamic library at link time, and all of its dependencies would be implicitly referenced as well. But static libraries lacked this feature, so if a link invocation was switched from using dynamic libraries to static libraries, the link command would usually fail unless it was rewritten to explicitly list the dependencies of the static library.
The GNU ar utility now supports a --record-libdeps option to embed dependency lists into static libraries as well, and the libdep plugin may be used to read this dependency information at link time. The dependency information is stored as a single string, carrying -l and -L arguments as they would normally appear in a linker command line. As such, the information can be written with any text utility and stored into any archive, even if GNU ar is not being used to create the archive. The information is stored in an archive member named ‘__.LIBDEP’.
For example, given a library libssl.a that depends on another library libcrypto.a which may be found in /usr/local/lib, the ‘__.LIBDEP’ member of libssl.a would contain
-L/usr/local/lib -lcrypto
My compiler does not enable the libdep linker plugin by default. Evidently neither does yours. Possibly
it will be enabled by default in stock GCC builds of the future. Meantime, to enable it you must locate
libdep.so (assuming you have it) in your system's bfd-plugins directory and pass its full path as argument to the -plugin linker
option in your linkage command.
Here is an illustration of the intended use of ar l together with enabled libdep.so. My tool versions are:
$ gcc --version | head -n1
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
$ ar --version | head -n1
GNU ar (GNU Binutils for Ubuntu) 2.42
$ ld --version | head -n1
GNU ld (GNU Binutils for Ubuntu) 2.42
Source files:
$ tail -n +1 foo.c bar.c main.c
==> foo.c <==
extern void bar(void);
void foo(void)
{
bar();
}
==> bar.c <==
#include <stdio.h>
void bar(void)
{
puts(__func__);
}
==> main.c <==
extern void foo(void);
int main(void)
{
foo();
return 0;
}
I'll make a library libbar from bar.c: it can be static or shared, so arbitrarily I'll make it shared.
I'll make a static library libfoo.a from foo.c, with a dependency on libbar, and insert the
linkage options to resolve that dependency into libfoo.a itself using ar l. Then link libfoo.a into
a program a.out without using those linkage options.
Make libfoo.a:
$ gcc -c foo.c
$ ar rcs l"-L. -lbar" libfoo.a foo.o
Here is the manifest of libfoo.a:
$ ar t libfoo.a
foo.o
__.LIBDEP
And here are the contents of the "magic" member __.LIBDEP:
$ printf "%s\n" "$(ar p libfoo.a __.LIBDEP)"
bash: warning: command substitution: ignored null byte in input
-L. -lbar
Make libbar.so:
$ gcc -shared -fPIC -o libbar.so bar.c
Link a.out:
$ gcc main.c -L. -lfoo -Wl,-plugin,/usr/lib/x86_64-linux-gnu/bfd-plugins/libdep.so,-rpath=$(pwd)
got deps for library ./libfoo.a: -L. -lbar
And run:
$ ./a.out
bar
If you are using the GNU toolchain and have or can get the libdep.so plugin, you can use it in this fashion to elide
the game engine dependencies in your linkages (likely the one essential that was missing from your own attempts was enabling the libdep.so linker plugin).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With