Learn Make

Swe03

Introduction

This series of exercises introduces you to the make build tool. An example project is provided without a makefile. It is up to you to write one.

The example program is an interpreter for a very simple lambda-calculus-like programming language, written in C. It reads in lambda-terms from standard input such as:

(\x . \y . x) ((\x . x) 1000) 2000
(where \ denotes lambda-abstraction) and reduces the term to normal form using a lazy rewriting strategy, e.g. yielding:
1000

The actual purpose of the program is not very important, though. More important are the following facts about the implementation:

  • The program consists of several C source files and header files such as main.c, abstract.h, etc.

  • The parser is implemented using the Bison parser generator which translates a Bison parser specificition --- parser.y --- into a C-language parser corresponding to the specified grammar.

  • Likewise, the lexical analyser (lexer) is implemented using the Flex lexer generator. The lexer is specified in lexer.l.

A shell script buildme.sh is provided that builds the program.

Exercises

  1. You can get the code for these exercises at http://www.cs.uu.nl/~eelco/interpreter-0.1.tgz. You can unpack this file as follows:
    gunzip < interpreter-0.1.tgz | tar xvf -
      

  2. Build the program by running buildme.sh. Try if the resulting program works, e.g.:
    $ ./interpreter < test-03.lam
    
    This should print out 1000.

    The problem with buildme.sh is that it is inefficient: it recompiles everything. This is not a problem for such a small program, but for real-world software this approach becomes intractable. In addition, the order of statements in buildme.sh is important; for example, bison must be run before main.c is compiled (why is this?).

  3. Write a simple makefile.

    Note: in makefiles, commands have to be prefixed with a tab character (not 8 spaces!).

  4. What about dependencies? What files depend on what other files?

  5. Use pattern rules to make the makefile shorter.

  6. Add a clean target to delete derived files.

  7. Add a install target to copy interpreter to some other location, e.g. /usr/bin.

  8. Rather than writing header file dependencies by hand, they should be generated automatically. You can use gcc -MM to let the C compiler generate a list of dependencies for a C file.

  9. Unit testing: add a check target that runs interpreter on every input file (*.lam) and checks that the output equals the corresponding output file (*.out). Pattern rules are useful here. You can use the cmp or diff Unix commands to check whether files are equal

Information