Any experienced C developer knows that the linker handles shared libraries and archive libraries very differently.
Maintaining correct library link order in hundreds (thousands) of makefiles is a maintenance nightmare that often ends up blocking noble efforts to refactor libraries.
If generally using shared libraries, people get slack about the link order, since it matters little - all symbols in all libs need to be resolved.
Then when they try to do profiling for example, they are using archive libraries again and link order matters; that previous laxness bites hard.
So what to do? Obvioulsy we take advantage of make's ability to solve dependency graphs. But how?
This is a simple makefile that can be included by lib.mk when building a library, and by prog.mk when consuming them.
When included by lib.mk we generate lib${LIB}.ldorder.inc which contains:
LDORDER_LIBS= ${LDORDER_LIBS} .include <ldorder.mk>
obviously we expand ${LDORDER_LIBS} in the generated makefile. The default is derrived from variables like DPADD and represents the libraries we know need to be linked after this library - because it depends on them.
This lib${LIB}.ldorder.inc gets staged alongside the library.
When we come to building an app, prog.mk includes ldorder.mk which again initializes LDORDER_LIBS based on DPADD etc.
We turn ${LDORDER_LIBS} into a set of targets:
_ldorders:= ${LDORDER_LIBS:T:R:C/\.so.*//:S,^,.ldorder-,} ${_ldorders}: _LDORDER_USE .ldorder: ${_ldorders}
Where _LDORDER_USE is a .USE target (think macro) that will output the relevant directives for ld, using the value of ${LDADD_lib${LIB}} the default being just -l${LIB}. The output is appended to a file .ldorder.
Then for each library we go looking for its corresponding .ldorder.inc file and include them, and the process repeats recursively.
Each time ldorder.mk is included from a *.ldorder.inc file we created a dependnecy:
_ldorder:= .ldorder-${.INCLUDEDFROMFILE:S,.ldorder.inc,,} ${_ldorder}: ${_ldorders}
thus we build a dependency graph of the .ldorder-* targets.
By the time we are done, we have a series of targets that when run will populate .ldorder with the set of linker directives we need, but in the opposite order from what we want.
Finally the target ldorder depends on .ldorder and simply reverses its content.
Thus prog.mk just makes ${PROG} depend on ldorder and we can use it to provide the list of optimally ordered linker directives - something like:
${PROG}: ${OBJS} ldorder ${CC} -o ${.TARGET} ${OBJS} `cat ldorder` ${LDADD}
This works well. Not only does it take care of the link order so profiling and such just work, it automatically adapts as libraries that we might not even know we need are refactored.
Of course there is always the risk of a cicular dependency between two or more libs that will break things. The only real fix is to combine or refactor the offending libraries. But we can often work around it by simply setting LDORDER_LIBS appropriately in one of the library makefiles.
For example; we can have one of the libs in the cycle omit another and add a header to its lib${LIB}.ldorder.inc, which is easily done. Just add something like this after the inlcude of lib.mk:
.if ${MK_LDORDER_MK} != "no" # circular dependencies require contortions until fixed ${libLDORDER_INC}: ldorder-header ldorder-header: .NOPATH echo LDADD_lib${LIB} = -l${LIB} -lother > ${.TARGET} .endif
the target that builds lib${LIB}.ldorder.inc will automatically consume any dependencies that match *ldorder*.
There are two options that control the use of ldorder.mk separately for lib.mk and prog.mk:
OPTIONS_DEFAULT_NO += LDORDER_MK OPTIONS_DEFAULT_DEPENDENT += PROG_LDORDER_MK/LDORDER_MK
This allows generating lib${LIB}.ldorder.inc even if they are not yet to be used. This is handy if the consumers of a tree might be using MK_PROG_LDORDER_MK=yes.
Sometimes we need to introduce barriers into the link order. For example, if we are making use of pre-published components which live outside our tree (or sandbox - ${SB}) we might want to introduce a linker directive like -Wl,-Bdynamic to avoid using archive libraries from the external components.
To introduce a barrier we need to have an artificial dependency, ldorder.mk defines LDORDER_EXTERN_BARRIER for this purpose. A makefile or local.ldorder.mk can define a target like:
${LDORDER_EXTERN_BARRIER}: @test -z "${extern_ldorders}" || \ echo -Wl,-Bdynamic >> .ldorder
and ldorder.mk will define a couple of variables to simplify hooking it into the dependencies.
extern_ldorders
lists the .ldorder-* targets that represent libraries external to ${SB}.
sb_ldorders
all the .ldorder-* targets excluding extern_ldorders
Thus in a makefile or more conveniently if this is to be done globally, in local.dirdeps-build.mk (assuming we are using the DIRDEPS_BUILD) we can have:
.ldorder ${sb_ldorders}: ${LDORDER_EXTERN_BARRIER} ${LDORDER_EXTERN_BARRIER}: ${extern_ldorders}
It is important that these targets appear after every other makefile that might affect .ldorder has been read. This makes local.dirdeps-build.mk ideal; since it is read from ${.MAKE.DEPENDFILE} via dirdeps.mk.
The net effect of all this is that ${LDORDER_EXTERN_BARRIER} will appear in the dependencies for .ldorder between those from within the SB and those external to it.
Author: | sjg@crufty.net /* imagine something very witty here */ |
---|