Peeyushpd
Peeyushpd

Reputation: 69

C Makefile for a source tree

Need help in writing a Makefile for below source tree. I have tried out simple examples for Makefile and those worked fine. But not able to figure out how do I write a Makefile for below kind of source tree.

I am organizing my code as below

root_dir:

    Makefile

    component1:
        dir1:
            file1.c file1.h 
        dir2:
            file2.c file2.h
        dir3:
            file3.c file3.h

    component2:
        dir4:
            file4.c file4.h
        dir5:
            file5.c file5.h

    common:
        debug.c debug.h
        utils.c utils.h
        main.c main.h

Here, main.c uses some functions declared in debug.h, utils.h, file1.h and file4.h. These file1.c and file4.c use the debug.h and utils.h. I have started writing the Makefile rules as below.

CC=gcc
CFlags=-c -g3 -Wall

myexec: main.o
        $(CC) main.o -o myexec

main.o: common/main.c common/main.h common/utils.h
        $(CC) $(CFLAGS) common/main.c

This Makefile gives "undefined reference" error messages for the functions declared in utils.h. It can crib about the functions declared in debug.h, file1.h or file4.h, but shouldn't have given the error message for functions from utils.h

Please help me in finding out what is wrong with the Makefile here.

Upvotes: 0

Views: 590

Answers (2)

Beta
Beta

Reputation: 99154

First let's fix the main.o rule:

main.o: common/main.c common/main.h common/utils.h
    $(CC) $(CFLAGS) common/main.c

If, as you say main.c uses some functions declared in debug.h, utils.h, file1.h and file4.h, then those headers should be prerequisites of the rule (so that if you modify a header, Make will rebuild main.o):

main.o: common/main.c common/main.h common/debug.h common/utils.h component1/dir1/file1.h component2/dir4/file4.h
    $(CC) $(CFLAGS) common/main.c

Now look at the myexec rule:

myexec: main.o
    $(CC) main.o -o myexec

It's no surprise that you get "undefined reference" errors; the functions declared in debug.h, utils.h, file1.h and file4.h are defined in (I presume) debug.c, utils.c, file1.c and file4.c, which this makefile never mentions. Do not proceed until you understand this.

The way to handle this is tho have the myexec rule link all of the relevant object files:

myexec: main.o debug.o utils.o file1.o file4.o
    $(CC) main.o debug.o utils.o file1.o file4.o -o myexec

Naturally you must have rules for debug.o, utils.o, file1.o and file4.o, similar to the one for main.o.

Once that is done, you have a makefile that works but is needlessly long and redundant. "Effective but crude". It can be made much shorter and more elegant, but this answer is getting long; just get it working, then we can work on making it cleaner.

Upvotes: 2

user3629249
user3629249

Reputation: 16540

here is a long example: it is for a linux embedded system, but will work with only minor changes for your application

These two makefiles will create an executable in each sub directory

it has a directory tree something like the following:

top
    utility .h and .c files
    makefile.top
    makefile.bot
    /executable1
        exec1 .h and .c files
    /executable2
        exec2 .h and .c files

contents of makefile.mak

SHELL = /bin/sh


#  note: this makefile.mak needs to be run from the ./src directory
# of the GOT4 directory tree


SRC := $(wildcard *.c)
OBJ := $(SRC:.c=.o)
DEP := $(SRC:.c=.d)
INC := $(SRC:.c=.h)


MAKE    :=  /usr/bin/make

CC      :=  /usr/bin/gcc

CP      :=  cp

MV      :=  mv

LDFLAGS :=  -L/usr/local/lib -L/usr/lib -L/lib

DEBUG   :=  -ggdb3

CCFLAGS :=  $(DEBUG) -Wall -Wextra -pedantic

#CPPFLAGS += =MD

LIBS    :=  -lssl -ldl -lrt -lz -lc -lm



.PHONY: AllDirectories


AllDirectories :=  \
    executable1 \
    executable2 



.PHONY: all

all: $(OBJ) $(AllDirectories)
    $(foreach d,$(AllDirectories), \
    ( cd $d && $(MAKE) -f ../makefile.bot name=Tsk_$d all ); )



#
# create dependancy files
#
%.d: %.c
    # 
    # ========= START $< TO $@ =========
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$;                      \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;     \
    rm -f $@.$$$$
    # ========= END $< TO $@ =========



#
# compile the .c file into .o files using the compiler flags
#
%.o: %.c %.d 
    # 
    # ========= START $< TO $@ =========
    $(CC) $(CCFLAGS) -c $< -o $@ -I. 
    # ========= END $< TO $@ =========
    # 



.PHONY: clean
#clean: $(AllDirectories)
#   # ========== start clean activities ==========
#   rm -f *.o
#   rm -f $(name).map
#   rm -f $(name)
#   rm -f *.d
#   $(foreach d,$(AllDirectories), \
#    ( cd $d && $(MAKE) -f makefile.mak clean ); )
#   # ========== end clean activities ==========

clean: $(AllDirectories)
    # ========== start clean activities ==========
    rm -f *.o
    rm -f $(name).map
    rm -f $(name)
    rm -f *.d
    rm -f ../bin/Tsk_*
    $(foreach d,$(AllDirectories), \
    ( cd $d && $(MAKE) -f ../makefile.bot name=Tsk_$d clean ); )
    # ========== end clean activities ==========



.PHONY: install
#install: $(AllDirectories)
#   # ========== start install activities ==========
#   $(foreach d,$(AllDirectories), \
#    ( cd $d && $(MAKE) -f makefile.mak clean ); )
#   # ========== end install activities ==========

install: $(AllDirectories)
    # ========== start install activities ==========
    $(foreach d,$(AllDirectories), \
    ( cd $d && $(MAKE) -f ../makefile.bot name=Tsk_$d install ); )
    # ========== end install activities ==========



# include the contents of all the .d files
# note: the .d files contain:
# <filename>.o:<filename>.c plus all the dependancies for that file 
# I.E. the #include'd header files
# wrap with ifneg... so will not rebuild *.d files when goal is 'clean'
#
ifneq "$(MAKECMDGOALS)" "clean"
-include $(DEP)
endif

and the makefile.bot

SHELL = /bin/sh


BINDIR  :=  /home/user/bin


.PHONY: all
all : $(BINDIR)/$(name) ../makefile.mak ../makefile.bot


#
# macro of all *.c files 
# (NOTE:
# (the following 'wildcard' will pick up ALL .c files
# (like FileHeader.c and FunctionHeader.c 
# (which should not be part of the build
# (so be sure no unwanted .c files in directory
# (or change the extension
#
SRC := $(wildcard *.c)
OBJ := $(SRC:.c=.o)
DEP := $(SRC:.c=.d)
INC := $(SRC:.c=.h)


COMMON_OBJ := $(wildcard ../*.o)
#COMMON_SRC := $(wildcard ../*.c)
#COMMON_OBJ := $(COMMON_SRC:.c=.o)
#COMMON_DEP := $(COMMON_SRC:.c=.d)
#COMMON_INC := $(COMMON_SRC:.c=.h)

MAKE    :=  /usr/bin/make

CC      :=  /usr/bin/gcc

CP      :=  cp

MV      := mv

LDFLAGS :=  -L/usr/local/lib

DEBUG   :=  -ggdb3

CCFLAGS :=  $(DEBUG) -Wall -Wextra -pedantic -std=c99

#CPPFLAGS += =MD

LIBS    :=   -lssl -ldl -lrt -lz -lc -lm



#
# link the .o files into the executable 
# using the linker flags
# -- explicit rule
#
$(name): $(OBJ) $(COMMON_OBJ) ../makefile.mak ../makefile.bot
    #
    # ======= $(name) Link Start =========
    $(CC) $(LDFLAGS) -o $@ $(OBJ) $(COMMON_OBJ) $(LIBS)
    # ======= $(name) Link Done ==========
    #



# note:
# using MV rather than CP results in all executables being re-made everytime
$(BINDIR)/$(name): $(name)
    #
    # ======= $(name) Copy Start =========
    sudo $(CP) $(name) $(BINDIR)/.
    # ======= $(name) Copy Done ==========
    #



#
#create dependancy files -- inference rule
# list makefile.mak as dependancy so changing makfile forces rebuild
#
%.d: %.c 
    # 
    # ========= START $< TO $@ =========
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$;                      \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;     \
    rm -f $@.$$$$
    # ========= END $< TO $@ =========



# 
# compile the .c file into .o files using the compiler flags
# -- inference rule
#
%.o: %.c %.d 
    # 
    # ========= START $< TO $@ =========
    $(CC) $(CCFLAGS) -c $< -o $@ -I. 
    # ========= END $< TO $@ =========
    # 



.PHONY: clean
clean: 
    # ========== CLEANING UP ==========
    rm -f *.o
    rm -f $(name).map
    rm -f $(name)
    rm -f *.d
    # ========== DONE ==========



.PHONY: install
install: all

# include the contents of all the .d files
# note: the .d files contain:
# <filename>.o:<filename>.c plus all the dependancies for that .c file 
# I.E. the #include'd header files
# wrap with ifneg... so will not rebuild *.d files when goal is 'clean'
#
ifneq "$(MAKECMDGOALS)" "clean"
-include $(DEP)
endif

Upvotes: 0

Related Questions