I220b Lab 2

$30.00 $15.00

Download Details:

  • Name: lab2-q20vsk.zip
  • Type: zip
  • Size: 107.71 KB

Category:

Description

5/5 - (1 vote)

1.1 Aims
The aim of this lab is to introduce you to the use of Makefile’s under Unix.
After completing this lab, you should be familiar with the following topics:
• The basic operation of make for building C programs.
• Common problems when using make.
• The use of make variables.
• The presence of implicit commands in make.
1.2 Background
A typical large program consists of multiple sub-systems and libraries. Each subsystem or library will contain multiple source les. Building the program entails
compiling all sub-systems and libraries with the correct options and assembling
them together. This can often be quite complex and time consuming. If any
source le changes, it should be possible to rebuild the program while redoing as
little work as possible. The make program allows the automation of such tasks.
The operation of make is controlled by a le typically named Makefile in the
directory where make is invoked.
Note that make is an example of a build tool. The make used in this lab is
typical of that found in Unix systems. Microsoft’s nmake is a similar program.
Build tools like Java’s ant, Ruby’s rake and Python’s scons have similar
functionality.
1.2.1 Principles
A Makefile basically consists of a set of rules. Each rule describes the prerequisite les for building a target le and the recipe which needs to be carried out
if any of the prerequisite les are newer than the target le.
target: prequisite …
recipe
1
The recipe can consist of multiple Unix shell commands (this can include compilation commands), which must be run to make the target from the prerequisite
les.
The target for one rule can be a prequisite for another rule. Hence the rst
rule will not be run until the prerequisite is made up-to-date by its rule. The
make program (at least the GNU version) tracks these dependencies across any
number of levels and executes all necessary recipes to bring the targets of all
relevant rules up-to-date.
To build a particular target, make can be invoked with that target as its
command-line argument. If invoked with no targets, it will attempt to build
the target for the rst rule in the Makefile.
Consider building an executable hello from 3 les: a hello module consisting
of a specication header le hello.h and an implementation le hello.c and
a main.c which includes the hello.h header le. This can be achieved using
the following Makefile:
hello: main.o hello.o
#link main.o and hello.o to executable hello
gcc main.o hello.o -o hello
hello.o: hello.c hello.h
#compile hello.c to object file hello.o
gcc -g -Wall -c hello.c
main.o: main.c hello.h
#compile main.c to object file main.o
gcc -g -Wall -c main.c
clean:
rm -f *~ *.o hello
Note the last target clean. It does not have any prerequisites and hence will run
its recipe whenever it is invoked (typically invoked explicitly as make clean).
It’s recipe runs the shell rm command which will remove all emacs backup les
specied by the wildcard pattern *~, all object les specied by *.o as well as
the built hello executable. The name clean is conventionally used for such
targets which clean-up les built by make as well as any garbage les.
1.2.2 Variables in make
Note that in the previous example, both the hello.o and main.o using the
compiler options -g to turn on debugging and -Wall to turn on reasonable
warnings. This is a violation of the DRY principle, since the same options were
2
repeated multiple times. Such violations can be avoided by the use of make
variables.
A make variable is dened on a line which consists of an identier VAR followed
by an = character which may be preceeded/followed by linear whitespace (i.e.
whitespace within the same line) followed by a denition. If the denition is
spread across multiple lines, then the last character must be a \ on all except
the last line of the denition.
The use of a variable VAR within a rule is indicated by $(VAR) and is replaced
by its denition. If a $ is to occur within a rule, then it must be quoted by
repeating it.
Additionally, within each rule, the special make variable $@ stands for the target
and the special make variable $< stands for the rst prerequisite and $^ stands
for all the prerequisites with spaces between them.
With the use of variables, the previous Makefile can become:
TARGET = hello
OBJS = \
main.o \
hello.o
CC = gcc
CFLAGS = -g -Wall
LDFLAGS =
$(TARGET): $(OBJS)
#link $(OBJS) to executable hello
$(CC) $(OBJS) $(LDFLAGS) -o $@
hello.o: hello.c hello.h
#compile hello.c to object file hello.o
$(CC) $(CFLAGS) -c $<
main.o: main.c hello.h
#compile main.c to object file main.o
$(CC) $(CFLAGS) -c $<
clean:
rm -f *~ *.o $(TARGET)
1.2.3 Implicit Rules
Note that in the previous example, the recipes for building both hello.o and
main.o are absolutely identical. In fact, a little thought will reveal that this
3
recipe can always be used for building a .o le from a .c le. So make contains
a set of implicit rule similar to this. If there is no recipe given for building a
prerequisite le, then make uses its implicit rules.
With the use of implicit rules, the Makefile can be simplied to:
TARGET = hello
OBJS = \
main.o \
hello.o
CC = gcc
CFLAGS = -g -Wall
LDFLAGS =
$(TARGET): $(OBJS)
#link $(OBJS) to executable hello
$(CC) $(OBJS) $(LDFLAGS) -o $@
clean:
rm -f *~ *.o $(TARGET)
hello.o: hello.c hello.h
main.o: main.c hello.h
Note that the rules specifying the dependencies for the .o le have been moved
to the bottom of the Makefile as they are purely declarative (using the implicit
rules). If it was not necessary to record the fact that both hello.o and main.o
also depended on hello.h, then the last two lines too could be removed as make
is capable of concluding that hello.o depends on hello.c and main.o depends
on main.c.
1.2.4 Gotcha’s
The make program evolved in the 1970’s when many programming languages
were line-oriented. Hence it has a line-oriented syntax with some very peculiar
syntax rules which can result in extremely painful gotcha’s for the unwary.
• The lines containing recipes MUST BEGIN WITH A TAB CHARACTER. Since most text editors do not distinguish between the display
of tab and space characters, this is a very common problem (the emacs
editor will warn you about suspicious lines).
• When make variable denitions or recipe commands extend over multiple lines, all but the last line must terminate with a \ character. There
CANNOT BE ANY SPACES after the \ character.
4
• Each command in a recipe is run in a separate shell. Hence a command
cannot aect the state of the shell for a subsequent command.
For example, the following rule attempts to delete all .o les in directory
dir:
clean-dir:
cd dir
rm -f *.o
This will not work. The rst command runs in a separate shell and changes
its current directory to dir, but then that shell terminates. The second
command runs in a new shell and will delete all *.o in the current directory, not the dir directory.
The x for this is to run both commands within a single shell as follows:
clean-dir:
cd dir; \
rm -f *.o
By using the trailing \ after the rst command, only a single shell is used
to run the sequential shell command cd dir; rm -f *.o which has the
desired eect.
1.3 Exercises
When the exercises mention a new Unix command you are unfamiliar with, it
is a good idea to do a man or google lookup on that command to get an idea
of its capabilities. Make sure you have followed the tips for setting up your
environment for this lab. Get a copy of all the lab2 les in your work/lab2
directory by copying the ~/cs220/labs/lab2/files directory into your newly
created lab2 directory:
$ mkdir work/lab2
$ cd work/lab2
$ cp -r ~/cs220/labs/lab2/files/* .
1.3.1 Exercise 1: No Makele
Change over to the ex1 directory.
$ cd ex1
$ ls -l
You should see that the directory contains a single hello.c le.
Simply type make in that directory. You should get an error message. However,
now try make hello. You should see that make automatically builds the hello
5
executable. Type ls -l to see the created le, use the command file hello
to conrm that it is an executable, and type ./hello to execute it. You should
see the usual hello world message.
How did make know how to build hello even though there is no Makefile in
the directory? The answer is by using implicit rules.
To see the list of implicit rules builtin to make, type make -p | less The
less command allows you to page back and forth through the output using the
spacebar and the b key respectively). You will see that the set of rules is quite
extensive. To see only the lines which contain the letters CC, type make -p |
grep CC (tje grep program lters out lines which do not match the pattern
given by its argument). You will see lines relevant to compiling C programs
(but you will also see lines related to YACC which is a parser-generator program.
1.3.2 Exercise 2: Makele with Syntax Error
Change over to the ex2 directory and type make. You should get an error. Fix
the error and retry. A greeting should be printed on your terminal.
1.3.3 Exercise 3: Multi-File Compilation
Change over to the ex3 directory and take a look at the les there. This directory
contains a program to solve quadratic equations.
Note that this le contains a README le. It is always a very good idea to have
a README le in the top-level directory of a project giving a rough idea of what
that project is.
Perform the following steps:
1. This directory contains a Makefile, but unfortunately it contains an error.
Simply type make in an attempt to build the top-level target. If you look
at the resulting make trace you should see that make successfully built
quadr.o and main.o, but got errors regarding a missing sqrt() function
when attempting to link the object les to produce the executable.
The linker needs to be told which libraries to search when linking. By
default it always searches the standard C library (corresponding to the
gcc linker option -lc) when linking (which is why you do not get link
errors for functions like printf() which are dened there). However,
the default does not include the math library which is where the sqrt()
function is dened. Hence to x the error simply change the denition of
the LDFLAGS make variable from empty to -lm to include the math library.
6
Retype make. You will still get an error. The problem is that in the
line $(CC) $(LDFLAGS) quadr.o main.o -o $@, the arguments are processed in the order they are mentioned; hence the math library specied
by LDFLAGS is searched before the the linker realizes that quadr.o has
an outstanding reference to the sqrt() function. The x is to search the
libraries after processing the object les; you can make that happen by
moving the $(LDFLAGS) after the quadr.o main.o object le names.
If you retry make after making the above change, you should get a successful build of the quadr executable. Test it by providing input lines with
each line containing whitespace-separated coecient triples (remember to
terminate your input with a control-D character to indicate end-of-le
on the terminal), or by redirecting standard input from test.data.
2. If you look at main.c and quadr.c, you will see that they both depend
on quadr.h. Hence if make is doing its job, it should recompile all les if
quadr.h changes.
Try this. Simply touch quadr.h. Using ls -l you should see that it has
a modication time newer than quadr.o and main.o. Now type make, you
would expect it to recompile all the les. Instead it will simply output a
message saying quadr is up-to-date. What went wrong?
The problem is that though make has implicit knowledge that main.o and
quadr.o depend on main.c and quadr.c, it does not know that main¬
.o and quadr.o depend on quadr.h. One solution would be to provide
the dependencies explicitly in the Makefile as in the example Makefile’s
listed in the Background section. However, as the project evolves, it is
very likely that these dependencies will no longer be correct. Fortunately,
there is a better way.
The C pre-processor used by gcc has a special switch -MM which will output
all the non-system dependencies of its command-line arguments. Simply
add the following lines to your Makefile (make sure that the recipe line
starts with a tab character):
depend:
$(CC) -MM $(CPPFLAGS) *.c
Now type make depend. The dependencies for all your C les should be
printed on your terminal. Cut and paste these dependencies at the end of
your Makefile. Preceed the dependencies by a suitable comment (starting
with a # character) like
#automatically generated dependencies produced by make depend
Now typing make should rebuild your entire project.
[This procedure is obviously extremely clumsy. The references (and possibly a future lab) provide better alternatives.]
7
3. When you complete a project, you may want to create a single le archive
which contains all the source les used for building the project. This is
known as a source distribution. In this step, we will modify our Makefile
to create a source distribution.
First we need to list out all the source les in the project. This can be done
by dening a make variable SRC_FILES which contains a space-separated
list of all the source les. Dene this variable after the denition of the
PROJECT variable in the Makefile.
It is a bad idea to use shell glob-patterns like *.c to match all .c les as
advanced projects may generate .c and .h les automatically and those
should not be included; instead list all les explicitly. You should include
all human-generated les in the project, including the README, test les
like test.data as well as the Makefile. It is probably a good idea to
list one le per physical line and combine those physical lines into a single
logical line by terminating all but the last one by a \.
Then add the following rule (before the automatically generated dependencies section which is conventionally always kept at the end):
dist: $(PROJECT).tar.gz
$(PROJECT).tar.gz: $(SRC_FILES)
tar -cf $(PROJECT).tar $(SRC_FILES)
gzip -f $(PROJECT).tar
The rst command in the recipe creates a tar archive of all the SRC_FILES.
The second command compresses the archive.
Create a source distribution by typing make dist.
Now test your distribution. Create a temporary directory and cd to it.
Then type tar -xzf DISTR_PATH where DISTR_PATH is the path
from your temporary directory to the created quadr.tar.gz distribution
le. You should then be able to make the project afresh in this temporary
directory by simply typing make.
When you have completed this exercise, cat Makefile to your terminal so as
to include it in the script you will show the TA.
1.3.4 Exercise 4: Makele from Scratch
In this exercise, you will create a Makefile from scratch. Change over to the ex4
directory. There you will nd a set of les which when built into an executable
allows addition, lookup and deletion of key/value pairs from a table of key/value
pairs, for alphanumeric keys and integer values. Look at the README for an
example log.
8
Use the Makefile from the previous exercise as a template to create a Makefile for this directory. The top-level target should create an executable named
key-value. You should use automatic dependency generation to record all dependencies. You should also provide a dist target which will build a source
distribution.
When you have the Makefile working, cat it to your terminal so as to include
it in the script you will show the TA.
1.4 References
GNU Make Manual.
Advanced Auto-Dependency Generation. The lab used the traditional method
with all its drawbacks.
Robert Mecklenburg, Managing Projects with GNU Make, O’Reilly, 2004.