I'm going to use a small example for reference. Consider a project with:
inner_definitions.o : inner_definitions.cpp inner_definitions.h
gcc $^ -o $@
inner_class_1.o : inner_class_1.cpp inner_class_1.h inner_definitions.h
gcc $^ -o $@
inner_class_2.o : inner_class_2.cpp inner_class_2.h inner_definitions.h
gcc $^ -o $@
outer_class.o : outer_class.cpp outer_class.h inner_class_1.h inner_class_2.h
gcc $^ -o $@
executable.o : executable.cpp executable.h outer_class.h
gcc $^ -o $@
executable : __?1__
__?2__
But filling in the blanks __?1__
for the linker dependencies and __?2__
for the linker command isn't easy.
In this small example, one could argue that its easy to see that __?1__ = inner_definitions.o inner_class_1.o inner_class_2.o outer_class.o executable.o
. However, this is clearly not a scalable solution as it forces each developer to understand all the dependencies of the code they are working with so they can figure out the dependencies by hand rather than by using the make
utility.
Another solution would be to have a different variable for each object file that listed all its downstream dependencies: i.e __?1__ = executable.o $(executable_dependencies)
. This is not a desired solution because it forces the makefile to be compiled in the specific way so the variables are only used when they are fully defined. Also, for really large applications these variables might exceed the maximum variable length.
Yet another solution is to use archive .a files for linking. In this case, we could construct an inner_class_1.a
that included both inner_defintions.o
and inner_class_1.o
, so it could be linked with any object file that needed inner_class_1.o
without forcing the developer to reconstruct the dependencies. This approach seems promising, but involves having many duplicate files. Also, it doesn't appear that the gcc
linker can handle nested archive files.
Is there another approach? What is the best approach? Can the gcc
linker handle nested archive files?
The job you're trying to automate (picking the right object files to satisfy all references) is usually left to the linker, using static libraries (".a" files) to group the candidate object files, just as you suggest.
An important detail you may be missing: If you pass the linker an archive, it will only link in those files from the archive that are actually needed. So you can create archives at a fairly coarse level of granularity without necessarily bloating all your executables -- the linker will pick just what it needs -- although can easily end up with needlessly slow builds if you take this approach too far.
The GNU linker will not pull objects out of nested libraries. If you want to make one big library by merging many small ones, you can do that with the "addlib" command in an ar script. That will give you a single archive containing all of the object files without any nested library structure.
If the duplication of having .o files and .a files containing the same object code lying around bothers you, the fine manual describes a way to have make update the archives "directly".
Your makefile must have a list of objects to link together, like so:
OBJ_FILES = inner_definitions.o inner_class_1.o inner_class_2.o \
outer_class.o executable.o
executable : $(OBJ_FILES)
gcc $^ -o $@
Someone must write this; gcc can't do it for you, Make can't do it for you. Not every developer on the project needs to know how to construct that list, only the one who writes that part of the makefile. All the others will use that makefile, and a developer who adds a new dependency (e.g. inner_class_3
) can add it to the list.
And if your makefile is lost in a fire and the only developer who knows all the dependencies is hit by a bus, it really isn't hard to reconstruct the list: when you try to make executable
, the linker complains that foo::bar()
is undefined, you grep around and discover that foo::bar()
is defined in inner_class_2.cpp
, you add inner_class_2.o
to the list. Repeat until the linker stops complaining.
P.S. Once that's in order, you can simplify the rest of the makefile quite a lot:
%.o: %.cpp %.h
gcc -c $< -o $@
inner_class_1.o inner_class_2.o : inner_definitions.h
outer_class.o : inner_class_1.h inner_class_2.h
executable.o : outer_class.h
EDIT:
- The method I suggested does not require listing every object file that can be made, just the ones that are actually needed to build `executable`; I inferred the list from your question.
- Passing extra object files to the linker makes no difference to the final executable, but it does lead to unnecessary rebuilding. For example, suppose you add `alien.o` to `OBJ_FILES`. Then if you modify `alien.cpp` and run `make executable`, it will rebuild `alien.o` and `executable` even though there's no real need to do so. Correction (thanks to slowdog): unnecessary object files go into the final executable as dead code-- but I'm still right about unnecessary rebuilding.
- Organizing object files into archives and shared libraries is often convenient, but doesn't really change anything here.
- I know of no robust way to automatically construct the object list -- that is, a way that could deal with problem cases such as when the same function is defined in two different source files. This could become a real problem if unit tests are involved. But you could do it within your makefile if you follow a simple naming convention.
- The trick for doing it within your makefile is a pretty advanced one. I honestly think you'd be better off doing this the simple way until you're more comfortable with the tools.
EDIT:
All right, here's an outline of the advanced technique.
First, consider all of the #included header files. It would be nice to have Make handle the dependencies instead of putting them in by hand, as in the makefile above. And this is a straightforward task: if X.cpp #includes Y.h (either directly or through some chain of #included header files), then X.o will depend on Y.h. This has already been worked out as "Advanced Auto-Dependency Generation". But if you follow a strict naming convention, you can take it a step further: if everything declared but not defines in X.h is defined in X.cpp, then by following the same tree of #include statements we should be able to construct a list of the needed object files, which will then be the dependencies of executable
.
This really is a lot to absorb at once, so I won't try to work through an example. I suggest you look over the document and see how it can generate the Y.h dependencies, then try applying it to the example makefile, then think about what the "step further" should do.
Later you can apply it to the test harness, where the object files are, e.g., outer_class.o
, stub_inner_class_1.o
, stub_inner_class_2.o
and test_outer_class.o
.