===== 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
cleandelete everything computer-generated
installput 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 [[rpms|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 [[mailto:linus.gasser@epfl.ch|Linus Gasser]] email-me for any questions.