Makefiles

This is a short introduction into Makefiles. For further information, you may go and have a good look at 'info make'. It will tell you lots of things. This HowTo is wrapped around the Etrans-project and thus explains the Makefiles used therein. The directory-structure of Etrans is as follows:

Etrans-+-Frontend
       +-Radio-+-BTS
               +-MS

There is source-code that has to be compiled in the Frontend, BTS and MS directory. In all these three directories, there is also something to install. I chose to have four Makefiles all in all. One in the Etrans directory and one in each Frontend, BTS and MS respectively. The Makefile in the Etrans directory calls the other three with the appropriate action to take. I also took care to prepare everything for an easy installation into an rpm-package.

Etrans

This is the main-Makefile that may be called using some simple arguments:

all
(re)make all
clean
delete everything computer-generated
install
put the compiled binaries in the appropriate directories

The environment-variable RPM_BUILD_ROOT is taken as a possible base-dir for installation. If it is set for example to /tmp/install all files will go into /tmp/install/opt/radio. This is very useful when building rpm-packages.

Variable initialisation

# The directories where we have things to do
SDIRS = Frontend Radio/MS Radio/BTS
# The base-directory for installation
RADIODIR = /opt/radio
# Additionaly, we take care about a variable called
# RPM_BUILD_ROOT, which is used when the source is packaged
# into an rpm.
INSTALLDIR = $(RPM_BUILD_ROOT)$(RADIODIR)
# This variable is propagated to all lower Makefiles
MAKEFLAGS += RADIODIR=$(RADIODIR) INSTALLDIR=$(INSTALLDIR)

You can see here how variables are defined in a Makefile. The only variable that is a bit special is MAKEFLAGS. Whenever one calls a Makefile in another directory, all variables in the MAKEFLAG are set for the other Makefile. So, if this Makefile callse Frontend/Makefile, in Frontend/Makefile it is possible to use RADIODIR as well as INSTALLDIR.

Rules

# Default rule. Only depends on rtl_O.mk
all: Radio/rtl_O.mk
        @for a in $(SDIRS); do \
          $(MAKE) -C $$a; \
        done

This is the first rule. Also called a target. As it is the first one encountered in the Makefile, it is the default. So if you call 'make' without any arguments, this rule is called.

The ampersand in front of the 'for' command makes it invisible to the user. This is a bash-command and executes everything between 'do' and 'done' for all arguments given after 'in'. In our example this is 'Frontend' 'Radio/MS' and 'Radio/BTS'. It is good practice to use $(MAKE) instead of a simple 'make'. The former ensures that important switches are given to the other Makefiles, too. Using the '-C' switch, it is possible to call Makefiles from other directories. Where in a normal shell-command one would use '$a', the '$' has to be escaped, which is done in a Makefile by using two '$'.

# Clean current and lower directories
clean:
        @for a in $(SDIRS); do \
          $(MAKE) -C $$a clean; \
        done

The only change is that this time we give an argument to the other Makefiles, so that all source-dirs are cleaned of computer-generated garbage.

# Install in the desired directories
install: all
# first off, call all other Makefiles recursively
        @for a in $(SDIRS); do \
          $(MAKE) -C $$a install; \
        done
# if we're installing for an rpm, we have to create the dirs
ifneq ($(RPM_BUILD_ROOT),)
        install -d $(RPM_BUILD_ROOT)/etc/profile.d
endif
# then install the scripts that take care of the env.-vars
        install -m 755 radio.sh radio.csh $(RPM_BUILD_ROOT)/etc/profile.d/
        @echo setenv RADIO $(RADIODIR) >> $(RPM_BUILD_ROOT)/etc/profile.d/radio.csh
        @echo "[ -z \"\$$RADIO\" ] && RADIO=\"$(RADIODIR)\"" >> \
         $(RPM_BUILD_ROOT)/etc/profile.d/radio.sh
        @echo export RADIO >> $(RPM_BUILD_ROOT)/etc/profile.d/radio.sh
# and, copy the files needed for the FPGAs
        install -d $(INSTALLDIR)/rbt $(RPM_BUILD_ROOT)/dev
        install -m 444 rbt/* $(INSTALLDIR)/rbt
# of course the start-scripts also need their place:
        install -m 755 Scripts/* $(INSTALLDIR)/bin
# IMPORTANT: Install daq-node
        test -e $(RPM_BUILD_ROOT)/dev/daq0 || mknod $(RPM_BUILD_ROOT)/dev/daq0 c 127 0

The first part is the description of the rule 'install', as usual, we call all underlying Makefiles with the argument 'install'. They then put the corresponding binary-files in the right directories.

When installing with the 'install' command, it is important to have the directories in place, if not 'install' quits with an error. As we want to install some files in /etc/profile.d, we have to create this directory in the case of an RPM-build (remember: an install during RPM-build is done in an empty directory). The 'ifneq' construct compares the RPM_BUILD_ROOT variable against an empty string.

The installation of the PATH is most easily done with two files in /etc/profile.d When bash or csh starts up, it searches all files in this directory and executes them one by one. bash takes *.sh and csh takes *.csh So all we have to do is put a radio.sh and a radio.csh that add the /opt/radio/bin directory to the path and also insert the RADIO environment variable that is used by the 'startup' file. All this is done using bash-functions and is pretty straightforward.

Last but not least we check for the /dev/daq0 link that is needed by the software radio in order to function correctly.

# a simple 
# rm -rf $(INSTALLDIR)
# might do the trick. But what if somebody puts RADIODIR to
# / ? As this would pretty bad, we delete more hardly
uninstall:
        @for a in $(SDIRS); do \
          $(MAKE) -C $$a uninstall; \
        done
        rm -rf $(INSTALLDIR)/rbt
        rm -f $(INSTALLDIR)/bin/{Start*,stop}
# This should protect eventual similar files
        rm -f $(RPM_BUILD_ROOT)/etc/profile.d/radio.*sh
# removes radio/lib radio/bin radio
        rmdir $(INSTALLDIR)/{lib,bin,} || echo "failed on rmdir"
        rm -f $(RPM_BUILD_ROOT)/dev/daq0

Whenever you want to get rid of the software radio, you may do so by calling 'make uninstall', and everything is deleted. I use this sometimes to look if the rpms install correctly…

Radio/rtl_O.mk: /usr/rtlinux-3.1/rtl.mk
        @sed -e "s/-O2/-O/" /usr/rtlinux-3.1/rtl.mk > Radio/rtl_O.mk

There is a special file provided by rtlinux, called rtl.mk It's to be included in the Makefile and sets the include-path to work with rtlinux. Unfortunatly it also sets an optimisation level of 2, which is too high for the code of the software-radio. This is why the '-O2' is replaced by '-O', and the rtl.mk file is written to rtl_O.mk in our local directory.

Frontend

The Frontend-Makefile is quite simple, no bad tricks are done. Let's have a look at it:

Implicit rules

CFLAGS = -I/usr/rtlinux/linux -I/usr/rtlinux/include \
 -I/usr/X11R6/include -I../Radio/DAQ/ -I../Radio/L1_DSP \
 -I. -I/usr/include
LDFLAGS = -lforms -lX11 -lm -lXpm -L/usr/X11R6/lib
CC = kgcc -g

These are some standard definitions of the options to pass to the C-compiler. In this Makefile we make use of some 'implicit rules', that is, some things that are common to all compilation of every C-file. In fact, when you tell the make-program to get a file.o, it will automatically search for file.c and then do:

$(CC) $(CFLAGS) file.c -o file.o

Variables and substitutions

SOURCES = mainForm.c showChannel.c showSignal.c callbacks.c showText.c inputs.c
OBJ = $(SOURCES:.c=.o)

This is my way to do it. First I define the variable SOURCES as all the .c-files I have in the Frontend-directory. I could've also written SOURCES = *.c, but if there would be some test.c, this would also be incorporated. The next line, OBJ = $(SOURCES:.c=.o) tells make to replace every occurance of “.c” in SOURCES with “.o”. Like this we don't have to re-write the OBJ-line.

Rules

all: make.dep $(OBJ) sradio.o startup

Here we have a first rule. As it is the first one encountered in the Makefile, it is also the default one. That is, when you type “make” without parameters, this is what is called. The target “all” depends on 4 prerequisites: “make.dep”, “$(OBJ)”, “sradio.o” and “startup”. If any one of these is out-dated, it will be redone.

Dependencies

dep make.dep:
        $(CC) -MM $(SOURCES) $(CFLAGS) > make.dep

Another rule. This time for two targets: “make.dep” AND for “dep”. There are no prerequisites given, as it is supposed to be called only on user's request. The line after make.dep: has to begin with a TAB-character. All lines beginning with a TAB-character are executed in the shell by “make”. So this actually calls the compiler and tells him to re-create a dependency-file. If you call “make dep”, it will re-create the dependency-file. This file holds all the dependencies of the source-files of the project. It is in the form

mainForm.o: mainForm.c /usr/include/forms.h mainForm.h
showChannel.o: showChannel.c /usr/include/forms.h sradio.h mainForm.h \
  showChannel.h
[...]

It is pretty useful as it allows the makefile to know what files have to be recompiled when something changed. It states for example that whenever mainForm.c changes, it has to re-create mainForm.o which is in an implicit rule and will be done like described further up.

Including files

The dependency-file changes over time. This is why it isn't written directly in the Makefile. Instead we “include” it. There is one caveat, though: after a “make clean”, the make.dep file isn't available, and “make” will tell you so. This is not hindering compilation, but not nice. So, there is a simple line to help that:

ifeq (make.dep, $(wildcard make.dep))
include make.dep
endif

“ifeq” means “if equal”. It takes two parameters that it searches for equality. The first one is the name make.dep (not the file), while the second one searches for all files called “make.dep” and returns them. So if the $(wildcard…) doesn't find anything, it returns zero, the equality doesn't hold and the line “include make.dep” isn't executed. This is a bit more nice.

Special cases

Always when there are some default rules, they won't fit all. Also in this small project, a couple of files need special treatment:

sradio.o: sradio.c
        $(CC) $(CFLAGS) -DLINUX -DUSER -Wall -O2 -c sradio.c

“sradio.c” includes some system-specific files that need extra definitions and special optimisation.

startup: $(OBJ) startup.o sradio.o

Another implicit rule: only the prerequisites are given for the target “startup”. The “make” program knows itself how to call the linker in order to make the executable. We help it a bit with the definition of “LDFLAGS” higher up.

clean:
        rm -f *.o startup make.dep

When everything else fails, it may be that all these rules didn't do what was intended. So it's good to call “make clean” and “make”.

(Un)installation

install: all
        install -D -m 6100 startup $(INSTALLDIR)/bin/startup

uninstall:
        rm -f $(INSTALLDIR)/bin/startup

The only catch here is the '-m 6100' argument for the install-program. This simply sets 'chmod a+s' the startup-program. Like this, normal users may start the 'startup' program, too.

BTS and MS Makefile

The BTS and the MS are more or less identical with the Frontend-Makefile for the biggest part of them. One speciality is in the fact, that both use the same directories for the sources, but that the objects of both are stored in a special directory. In order to switch between the two compiling-styles, a flag is set during compilation, using the '-D' option from gcc. So, let's have a look at how to use make's capabilities for accomplishing this task:

Variable declaration

include ../rtl_O.mk
CC = kgcc
L1_PATH = ../L1_DSP
DAQ_PATH = ../DAQ
L2_PATH = ../L2
CFLAGS += -DMS -I$(DAQ_PATH)/ -I$(L1_PATH)/ -I$(L2_PATH) -DTWOFOUR -DLINUX $(INCLUDE)
L1_SOURCES = L1_ms_thread.c L1a_thread.c L1_misc.c  L1_mapping.c L1_spreading.c \
        L1_midamble.c L1_filter.c L1_synch_prim.c L1_data.c L1_slot.c \
        L1_main_functions.c L1_channel_estimation.c L1_matched_filter.c \
        L1a_decode.c L1a_parity.c L1a_xmit.c L1_Gain_Control.c \
        L1_Adjust_Sync.c
DAQ_SOURCES = daq.c daq_gains.c daq_freq.c daq_test.c fpga_setup.c
SOURCES := $(L1_SOURCES:%=$(L1_PATH)/%) $(DAQ_SOURCES:%=$(DAQ_PATH)/%)
L1_OBJ = $(L1_SOURCES:.c=.o)
DAQ_OBJ = $(DAQ_SOURCES:.c=.o)
OBJ = $(L1_OBJ) $(DAQ_OBJ)

The first interesting declaration is CFLAGS. As rtl_O.mk defines itself the CFLAGS variable, we need to add to it, using '+='`. There we see the flag '-DMS' that is used to tell the source that it is compiled for a MobileStation. The flag '-DTWOFOUR' sets some structures to be used with linux-2.4.x.

Then we have SOURCES. First of all, we define it statically, that is once and for all. ':=' implies this. If L1_SOURCES is changed at a later time, SOURCES won't change. After this we have an interesting substitution in the definition of SOURCES: '%=$(L1_PATH)/%'. The '%' sign is about the same as the '*' is to bash. It fit's everything. But, as this is 'make', it fits everything for one file. So, first it fits 'L1_ms_thread.c', then 'L1a_thread.c' and so on. This is changed to '$(L1_PATH)/%'. Here, the '%' sign stands for the part that it matched before. So, 'L1_ms_thread.c' changes to '../L1_DSP/L1_ms_thread.c'.

The substitution in L1_OBJ is already explained further up. Let's just say that this is equivalent to

L1_OBJ = $(L1_SOURCES:%.c=%.o)
DAQ_OBJ = $(DAQ_SOURCES:.c=.o)
OBJ = $(L1_OBJ) $(DAQ_OBJ)

Makefiles are very nice, once they are understood!

Dependencies across directories

all: make.dep $(OBJ) daq_module.o SR_driver.o

dep make.dep:
        $(CC) -MM $(SOURCES) $(CFLAGS) > make.dep

ifeq (make.dep, $(wildcard make.dep))
include make.dep
endif

The tricky part here is to understand how the '-MM' switch of gcc works. Let's look at one of the dependencies generated by gcc:

L1_bs_thread.o: ../L1_DSP/L1_bs_thread.c \
 /usr/src/rtlkernel-2.4.4-rtl/include/linux/modversions.h ../DAQ/daq.h \
 ../DAQ/daq_ext.h ../L1_DSP/L1_structs.h ../L1_DSP/L1a_structs.h \
 ../DAQ/daq_proto.h ../L1_DSP/L1_proto.h ../L1_DSP/L1_extern.h

So, here we see that gcc writes it the way we want it to (magic): our object file shall be in the current directory, and it's dependency have to be in the corresponding source-directories.

More rules

Now that we have defined the sources and the OBJs, we could think that 'make' may use one of its famous explicit rules and finish the job. Unfortunatly this is too complicated for 'make'. Or this is not the standard case. What make does without further intervention, is to compile everything and put the object-files into the source-directory. This is not what we want, as the BS will also write it's file in the same directory, and thus it would clash. So we have to tell 'make' that it has to write the object files in our own directory:

$(L1_OBJ):
        $(CC) $(CFLAGS) -c $(L1_PATH)/$(@:.o=.c)

$(DAQ_OBJ):
        $(CC) $(CFLAGS) -c $(DAQ_PATH)/$(@:.o=.c)

Of course 'make' allows us to set variables as rules. This way everytime make wants to get one of the objects, it falls into the corresponding rule and calls 'gcc' with the appropriate variables. The third argument here is another substitution: '@' is the current target's name. So if 'make' decided to redo 'L1_ms_thread.o', '$(@)' is substituted with this, and we tell make to substitute the .o into .c so that in the end a call is done like

$(CC) $(CFLAGS) -c ../L1_DSP/L1_ms_thread.c

which puts the resulting object-file in the current directory.

SR_driver.o : $(L2_PATH)/SR_driver.c
        $(CC) $(CFLAGS) -c $(L2_PATH)/SR_driver.c

daq_module.o : $(OBJ)
        $(LD) -r $(OBJ) -o daq_module.o

Two rules that you should understand now. Only speciality is the call to $(LD), which defaults to 'ld'. The switch '-r' permits the linker to generate modules that are readable by the kernel.

And the rest

install: all
        install -d $(INSTALLDIR)/lib/BTS
        install -m 444 SR_driver.o daq_module.o $(INSTALLDIR)/lib/BTS

uninstall:
        rm -rf $(INSTALLDIR)/lib/BTS

clean:
        rm -f *.o $(L1_PATH)/*.o $(DAQ_PATH)/*.o make.dep

Again, nothing new here. The 'Makefile' in 'Radio/BTS' is about the same structure, the only difference is the switch all at the beginning, where CFLAGS is defined a bit differently:

CFLAGS += -DBTS -I$(DAQ_PATH)/ -I$(L1_PATH)/ -I$(L2_PATH) -DTWOFOUR -DLINUX $(INCLUDE)

The '-DBTS' prepares the source for compiling as a Base Station.

This was a short introduction to make and Makefiles, brought to you by Linus Gasser email-me for any questions.

Last modified:: %2007/%02/%19 %09:%Feb