cgccopenglmakefilearchive

C makefile with multiple archives


I have a directory that contains both headers and source for a small collection of hobby OpenGL programs. My goal is create two main header files: "toolbox.h" and "geometry.h". The second #includes the first. I would also like the sources associated with these two headers to be lumped as two archives for convenience. When I make 01_Planet-Geom, none of the symbols from "toolbox.h" are found, despite me making its archive part of the make rule. make 00_Dyn-6D works just fine.

What do I need to change in order to make 01_Planet-Geom?

I get the following output:

gcc -c -O3 -Wall -std=gnu17  01_Planet-Geom.c
gcc -c -O3 -Wall -std=gnu17  TriNet.c
ar -rcs geometry.a toolbox.a TriNet.o
gcc -O3 -Wall -std=gnu17 -o 01_Planet-Geom.out 01_Planet-Geom.o toolbox.a geometry.a -lglut -lGLU -lGL -lm
/usr/bin/ld: geometry.a(TriNet.o): in function `N_from_VF':
TriNet.c:(.text+0x151): undefined reference to `get_CCW_tri_norm'

... and many more like it. These are all functions defined in "vector-f_ops.c" ...

/usr/bin/ld: TriNet.c:(.text+0x1abd): undefined reference to `dot_vec4f'
collect2: error: ld returned 1 exit status
make: *** [makefile:50: 01_Planet-Geom] Error 1

I have the following in "makefile":

########## COMPILATION SETTINGS ##################

CFLG=-O3 -Wall -std=gnu17
LIBS=-lglut -lGLU -lGL -lm
CLEAN=rm -f *.out *.o *.a


########## DEPENDENCIES ##########################

### Prog Dependencies ###
00_Dyn-6D.o: 00_Dyn-6D.c toolbox.h
01_Planet-Geom.o: 01_Planet-Geom.c toolbox.h geometry.h

### Float Matrix Math ###
matrix4x4f.o: matrix4x4f.c matrix4x4f.h

### OpenGL Toolbox ###
OGL_utils.o: OGL_utils.c toolbox.h
load_assets.o: load_assets.c toolbox.h
vector-f_ops.o: vector-f_ops.c toolbox.h

### Geometry Construction ###
TriNet.o: TriNet.c toolbox.h geometry.h


########## ARCHIVES & GENERAL RULES ##############

toolbox.a: OGL_utils.o load_assets.o vector-f_ops.o
    ar -rcs $@ $^

geometry.a: toolbox.a TriNet.o
    ar -rcs $@ $^

# Compile rules
.c.o:
    gcc -c $(CFLG)  $<


########## PROGRAM & CLEAN RULES #################

##### Program Rules ##############################

00_Dyn-6D: 00_Dyn-6D.o toolbox.a
    gcc $(CFLG) -o $@.out $^ $(LIBS)

01_Planet-Geom: 01_Planet-Geom.o toolbox.a geometry.a
    gcc $(CFLG) -o $@.out $^ $(LIBS)


##### Clean Rule #################################

#  Clean
clean:
    $(CLEAN)

I double checked that all the source files #include the appropriate headers.

"TriNet.c" has compiled successfully in a different context. I confirmed that make 00_Dyn-6D still works after adding the rules for 01_Planet-Geom and geometry.a.


Solution

  • Your linkage fails for a combination of two reasons.

    The first one

    is this:

    ar -rcs geometry.a toolbox.a TriNet.o
    

    which results from the Makefile rule:

    geometry.a: toolbox.a TriNet.o
        ar -rcs $@ $^
        
    

    You might believe that ar -rcs geometry.a toolbox.a TriNet.o has the effect of extracting all the object files from the archive toolbox.a and adding them to the target archive geometry.a, together with the object file TriNet.o.

    In fact, it simply archives the files toolbox.a and TriNet.o into geometry.a, with result that that geometry.a is an archive containing another archive, toolbox.a, and one object file TriNet.o.

    When an archive such as geometry.a is input to the linker it only considers object files that are members of the archive, as listed by ar t geometry.a. It will extract from the archive just those member object files that provide definitions of undefined symbols previously in the output file and statically link them into the output file, doing this iteratively until the archive yields no more needed definitions.

    The linker will not consider object files that are members of archives that are members of geometry.a. Therefore what geometry.a can contribute to your linkages is just TriNet.o, which of course is not enough.

    If you want geometry.a to have as members all the object files in toolbox.a as well as TriNet.o then you would need to make geometry.a from the same object files that you put into toolbox.a, plus TriNet.o.

    If ar -rcs geometry.a toolbox.a TriNet.o worked as it seems you expect it to, then it would be unnecessary to link both geometry.a and toolbox.a into programs requiring geometry.a and you would need some other reason for toolbox.a to exist (which it seems you do).

    But evidently you still think it necessary to input both of them in the linkage of your program 01_Planet-Geom.out.

    gcc -O3 -Wall -std=gnu17 -o 01_Planet-Geom.out 01_Planet-Geom.o toolbox.a geometry.a -lglut -lGLU -lGL -lm
    

    whose linkage fails:

    /usr/bin/ld: geometry.a(TriNet.o): in function `N_from_VF':
    TriNet.c:(.text+0x151): undefined reference to `get_CCW_tri_norm'
    

    Which brings us to:

    The second one

    geometry.a contains only the object file TriNet.o as far as the linker can see. But TriNet.o contains an undefined reference to get_CCW_tri_norm, which (I bet) is defined in a member of toolbox.a.

    You have input toolbox.a to the linkage but you have input it before the geometry.a. So when the linker inspected toolbox.a it was aware of no undefined reference to get_CCW_tri_norm and therefore did not seek, extract, or link any object file from toolbox.a that provides a definition of get_CCW_tri_norm. No file file that is subsequently input provides that definition, so the symbol remains undefined at end of linkage.

    Files that introduce undefined symbols to the linkage must be given to the linker before libraries that provide their definitions. The linker will inspect a library only to find definitions of undefined symbols already in hand, and does not look backwards.