File Dependencies: make

Steven J Zeil

Last modified: Feb 24, 2014

Contents:
1. The make Command
2. makefiles
2.1 Rules
2.2 Variables
2.3 Implicit Rules and Patterns
3. Working with Make
3.1 Touching Files
3.2 Artificial Targets
3.3 Dependency Analysis
3.4 Managing Subproject Builds
4. Case Studies:
4.1 C++ Spreadsheet Model
4.2 Assignments

make is a command/program that enacts builds according to a dependency graph expressed in a makefile.

1. The make Command

make CppJavaScanner

make Options

Some useful options:

-n
Print the commands that make would issue to rebuild the target, but don’t actually perform the commands.
-k
“Keep going.” Don’t stop the build at the first failue, but continue building any required targets that do not depend on the one whose construction has failed.
-f filename
Use filename instead of the default makefileMakefile

2. makefiles

At its heart, a makefile is a collection of rules.

2.1 Rules


The Components of a Rule

<span class="replaceable" markdown="1">target</span>: <span class="replaceable" markdown="1">dependencies</span>
        <span class="replaceable" markdown="1">commands</span>

where


Rule Examples

codeAnnotation.jar: code2HTML.class CppJavaScanner.class
        jar tvf codeAnnotation.jar code2HTML.class CppJavaScanner.class

CppJavaScanner.class: CppJavaScanner.java
        javac CppJavaScanner.java

code2HTML.class: code2HTML.java CppJavaScanner.java
        javac code2HTML.java

CppJavaScanner.java: code2html.flex
        java -cp JFlex.jar JFlex.Main code2html.flex


Why is This Better than Scripting?

code2html.javamake

How make Works

2.2 Variables

A makefile can use variables to simplify the rules or to add flexibility in configuring the makefile.


Referencing Variables

CppJavaScanner.class: CppJavaScanner.java
        javac $(JOPTIONS) CppJavaScanner.java

code2HTML.class: code2HTML.java CppJavaScanner.java
        javac $(JOPTIONS) code2HTML.java


Adding Power to Variables

GNU make adds some special extensions useful in setting up variables.


Example: Using variables

This allows us to write a “generic” rule for compiling C++ programs:

PROGRAM=myProgramName
SOURCEFILES=$(wildcard src/*.cpp)
OBJFILES=$(SOURCEFILES:%.cpp=%.o)

$(PROGRAM): $(OBJFILES)
        g++ -o $(PROGRAM) $(OBJFILES)

2.3 Implicit Rules and Patterns


Implicit Rules

An implicit rule looks like

.ext1.ext2:
        commands

where ext1 and ext2 are file extensions, and commands are the commands used to convert a file with the first extension into a file wit hthe second.

Example:

.cpp.o:
        g++ -g -c $<


Using Implicit Rules

The extensions used in implicit rules must be declared:

.SUFFIXES: .cpp .o

An implicit rule will be used when a target ends in one of these suffixes and


Implicit Rule Example

PROGRAM=myProgramName
SOURCEFILES=src/main.cpp src/adt.cpp
OBJFILES=$(SOURCEFILES:%.cpp=%.o)
.SUFFIXES: .cpp .o

.cpp.o:
        g++ -g -c $<

$(PROGRAM): $(OBJFILES)
        g++ -o $(PROGRAM) $(OBJFILES)

src/adt.o: adt.cpp adt.h


Pattern Rules

A pattern rule looks like a regular rule, but uses ‘%’ as a wildcard in the target and one of their dependencies:

src/test/java/%.class: src/test/java/%.java junit4.jar
        javac -cp junit4.jar -g src/test/java/$*.java

3. Working with Make

3.1 Touching Files

Modification Dates

make

Although this is fairly robust, there are ways to fool make


Touching a File

code2html.flex

Sometimes this is a useful thing to do on purpose.


Inadvertant Touches

Suppose we had our code annotation project in a directory project1 and did the following:

> cd project1
> make
> cd ..
> cp -rf project1 project2
> cd project2
> make

What would be re-built by the second make?


Inadvertant Touches

Suppose we had our code annotation project in a directory project1 and did the following:

> cd project1
> make
> cd ..
> cp -rf project1 project2
> cd project2
> make

Created != Success

3.2 Artificial Targets


Fooling make Again

A creative way to fool make:

What happens if we give a rule whose commands never actually create the target?

target: dependency1 dependency2
        echo Nope. Not going make that target!


Artificial Targets

We can take advantage of this trick by adding artificial targets that serve as the names for tasks to be performed.

build: codeAnnotation.jar

install: build
        cp codeAnnotation.jar $(INSTALLDIR)

clean:
        rm *.class CppJavaScanner.java

codeAnnotation.jar: code2HTML.class CppJavaScanner.class
        jar tvf codeAnnotation.jar code2HTML.class CppJavaScanner.class

CppJavaScanner.class: CppJavaScanner.java
        javac CppJavaScanner.java

code2HTML.class: code2HTML.java CppJavaScanner.java
        javac code2HTML.java

CppJavaScanner.java: code2html.flex
        java -cp JFlex.jar JFlex.Main code2html.flex


Common Artificial Targets

all
Often made the first rule in the makefile so that it becomes the default. Builds everything. May also run tests.
build
Build everything.
install
Build, then install
test
Build, then run tests
clean
Delete everything that would have been produced by the makefile in a build or test run.

3.3 Dependency Analysis


Dependency Analysis

Coming up with a list of depedencies (and keeping it current) can be troublesome.


Self-Building Makefile

selfBuilding.listing

3.4 Managing Subproject Builds

Subprojects are generally handled by giving each subproject its own makefile and using a master makefile to invoke the artificial targets:

all:
        cd model; make
        cd vcncurses; make
        cd vcjava; make

clean:
        cd model; make clean
        cd vcncurses; make clean
        cd vcjava; make clean

4. Case Studies:

4.1 C++ Spreadsheet Model


C++ Spreadsheet Model

ssModel.listing

4.2 Assignments

Setting up an assignment for a course:


include ../make.base
MAINPROG=testpicture
#
include ../cppMake.head

Tests/test%.out: Tests/test%.dat Work/${MAINPROG}
    cd $(WORKDIR); /bin/sh ../Tests/test$*.dat
    mv $(WORKDIR)/test$*.out $@

include ../cppMake.tail



make.base

DIR=${PWD}
ASST=$(notdir ${DIR})
WINEXE=.exe
UNIXEXE=
ifneq (,$(findstring MinGW,$(PATH)))
DISTR=MinGW
EXE=$(WINEXE)
WORKDIR=winwork
else
DISTR=Linux
EXE=$(UNIXEXE)
WORKDIR=Work
endif
HTMLDIR=/home/zeil/courses/cs330/webcourse/Assts/
INSTALLDIR=/home/zeil/courses/cs330/Assignments/$(ASST)


cppmake.head


#
PUBLICFILESx=$(wildcard Public/*)
PUBLICFILES=$(filter-out %~, ${PUBLICFILESx})
PUBLIC=$(notdir ${PUBLICFILES})
SOLUTIONFILES=$(wildcard Solution/*.h) $(wildcard Solution/*.cpp)
TESTDATFILES=$(wildcard Tests/test*.dat)
TESTDAT=$(notdir ${TESTDATFILES})
TESTOUTFILES=$(TESTDATFILES:%.dat=%.out)
SOLUTIONTESTDAT=$(TESTDAT:%.dat=Solution/%.dat)
SOLUTIONTESTOUT=$(TESTDAT:%.dat=Solution/%.out)
INSTALLEDFILES=$(PUBLIC:%=$(INSTALLDIR)/%)
WORKMAIN=Work/$(MAINPROG)
#

all:  Work/$(MAINPROG) ${TESTOUTFILES} ${SOLUTIONTESTDAT} ${SOLUTIONTESTOUT} 



cppmake.tail

cppMake.tail.listing