make is a command/program that enacts builds according to a dependency graph expressed in a makefile.
make devised by Dr. Stuart Feldman of Bel Labs in 1977
It has long been a standard component of *nix systems
make looks for its instructions in a file named, by default, makefile or Makefile
The make command can name any file in the graph as the target to be built, e.g.,
make CppJavaScanner
If no target is given, make builds the first file described in the makefile
At its heart, a makefile is a collection of rules.
A rule describes how to build a single file of the project.
Each rule indicates
Rules may appear in any order
The Components of a Rule
target: dependencies
commands
where
target is the target file,
dependencies is a space-separated list of files on which the target is dependent
commands is a set of zero or more commands, one per line, each preceded by a Tab character.
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?
Only one javac will be issued, after which the jar command is run.
make has determined the minimum number of steps required to rebuild after a change.
How make Works
Construct the dependency graph from the target and dependency entries in the makefile
Do a topological sort to determine an order in which to construct targets.
For each target visited, invoke the commands if the target file does not exist or if any dependency file is newer
TARGET=codeAnnotation.jar
SRC=src/main/java ➀
CLASSDEST=build/classes
JARDEST=build
TESTSRC=src/test/java
TESTCLASSDEST=build/testclasses
JAVA=$(shell find $(SRC) -type f -name '*.java') ➁
TESTJAVA=$(shell find $(TESTSRC) -type f -name '*.java')
CLASSES=$(patsubst $(SRC)/%, $(CLASSDEST)/%, $(JAVA:%.java=%.class))
TESTCLASSES=$(patsubst $(TESTSRC)/%, $(TESTCLASSDEST)/%, $(TESTJAVA:%.java=%.class))
TESTCLASSNAMES=$(subst /,.,$(subst $(TESTSRC)/, ,$(TESTJAVA:%.java=%)))
.SUFFIXES:
#
# Targets:
#
all: test $(JARDEST)/$(TARGET)
build/setup: ➂
-mkdir -p $(JARDEST)
-mkdir -p $(CLASSDEST)
-mkdir -p $(TESTCLASSDEST)
date > $@
$(JARDEST)/$(TARGET): $(CLASSES) ➃
cd $(CLASSDEST); jar cf temp.jar *
mv $(CLASSDEST)/temp.jar $@
test: $(TESTCLASSES) $(CLASSES) ➄
java -cp lib/junit-4.10.jar:lib/junit/hamcrest-core-1.1.jar:$(CLASSDEST):$(TESTCLASSDEST) org.junit.runner.JUnitCore $(TESTCLASSNAMES)
$(CLASSDEST)/%.class: $(SRC)/%.java build/setup ➅
javac -g -cp $(CLASSDEST) -d $(CLASSDEST) -sourcepath $(SRC) $(SRC)/$*.java
$(TESTCLASSDEST)/%.class: $(TESTSRC)/%.java build/setup $(CLASSES)
javac -g -cp $(CLASSDEST):lib/junit-4.10.jar:lib/junit/hamcrest-core-1.1.jar -d $(TESTCLASSDEST) -sourcepath $(TESTSRC) $(TESTSRC)/$*.java
clean:
-rm -f build
➀ Symbolic names for directories make it easier to rearrange layout.
➁ Various functions for manipulating and rewriting lists of files
➂ Create output directories. Note that this appears as a dependency in most of the later compilation rules (to guarantee it’s done before compiling).
➃ Build a Jar file
➄ Run the unit tests
➅ Compile .cpp
files.
This project adds a stage before compilation to generate some of the source code that then needs to be compiled.
TARGET=codeAnnotation.jar
SRC=src/main/java
CLASSDEST=build/classes
JARDEST=build
TESTSRC=src/test/java
TESTCLASSDEST=build/testclasses
GENSRC=build/gen/java
GENDEST=build/classes
FLEXSRC=src/main/jflex
JAVA=$(shell find $(SRC) -type f -name '*.java')
TESTJAVA=$(shell find $(TESTSRC) -type f -name '*.java')
CLASSES=$(patsubst $(SRC)/%, $(CLASSDEST)/%, $(JAVA:%.java=%.class))
TESTCLASSES=$(patsubst $(TESTSRC)/%, $(TESTCLASSDEST)/%, $(TESTJAVA:%.java=%.class))
TESTCLASSNAMES=$(subst /,.,$(subst $(TESTSRC)/, ,$(TESTJAVA:%.java=%)))
GENJAVA=$(GENSRC)/CppJavaScanner.java $(GENSRC)/CppJavaTeXScanner.java \
$(GENSRC)/ListingScanner.java $(GENSRC)/ListingTeXScanner.java
GENCLASSES=$(GENDEST)/CppJavaScanner.class $(GENDEST)/CppJavaTeXScanner.class \
$(GENDEST)/ListingScanner.class $(GENDEST)/ListingTeXScanner.class
.SUFFIXES:
#
# Targets:
#
all: test $(JARDEST)/$(TARGET)
build/setup:
-mkdir -p $(JARDEST)
-mkdir -p $(GENSRC)
-mkdir -p $(CLASSDEST)
-mkdir -p $(TESTCLASSDEST)
date > $@
$(JARDEST)/$(TARGET): $(CLASSES)
cd $(CLASSDEST); jar cf temp.jar *
mv $(CLASSDEST)/temp.jar $@
test: $(TESTCLASSES) $(CLASSES)
java -cp lib/junit-4.10.jar:lib/junit/hamcrest-core-1.1.jar:$(CLASSDEST):$(TESTCLASSDEST) org.junit.runner.JUnitCore $(TESTCLASSNAMES)
$(GENSRC)/CppJavaScanner.java: $(FLEXSRC)/code2html.flex build/setup
java -jar lib/jflex-1.4.3.jar -d $(GENSRC) $<
$(GENSRC)/CppJavaTeXScanner.java: $(FLEXSRC)/code2tex.flex build/setup
java -jar lib/jflex-1.4.3.jar -d $(GENSRC) $<
$(GENSRC)/ListingScanner.java: $(FLEXSRC)/list2html.flex build/setup
java -jar lib/jflex-1.4.3.jar -d $(GENSRC) $<
$(GENSRC)/ListingTeXScanner.java: $(FLEXSRC)/list2tex.flex build/setup
java -jar lib/jflex-1.4.3.jar -d $(GENSRC) $<
$(GENDEST)/%.class: $(GENSRC)/%.java build/setup
javac -g -d $(GENDEST) -sourcepath $(GENSRC) $(GENSRC)/$*.java
$(CLASSDEST)/%.class: $(SRC)/%.java build/setup $(GENCLASSES)
javac -g -cp $(CLASSDEST) -d $(CLASSDEST) -sourcepath $(SRC) $(SRC)/$*.java
$(TESTCLASSDEST)/%.class: $(TESTSRC)/%.java build/setup
javac -g -cp $(CLASSDEST):lib/junit-4.10.jar:lib/junit/hamcrest-core-1.1.jar -d $(TESTCLASSDEST) -sourcepath $(TESTSRC) $(TESTSRC)/$*.java
clean:
-rm -f build
Because this project is divided into multiple subprojects, we will have multiple makefiles:
In the project root directory:
all:
$(MAKE) -C gtest
$(MAKE) -C lib
$(MAKE) -C exe
clean:
$(MAKE) -C gtest
$(MAKE) -C lib
$(MAKE) -C exe
TARGET=libmain.a ➀
CXX=g++
CXXFLAGS=-g -pthread -I ../gtest/include -I src/main/headers -std=c++11
LINK=$(CXX)
LFLAGS=../gtest/build/libs/libgtest.a -lpthread
SRC=src/main/cpp
OBJDEST=build/objs/main
DEST=build/libs
EXEDEST=build/exe
TESTSRC=src/mainTest/cpp
TESTOBJDEST=build/objs/test
CPP=$(wildcard $(SRC)/*.cpp)
OBJS=$(patsubst $(SRC)/%, $(OBJDEST)/%, $(CPP:%.cpp=%.o))
TESTCPP=$(wildcard $(TESTSRC)/*.cpp)
TESTOBJS=$(patsubst $(TESTSRC)/%, $(TESTOBJDEST)/%, $(TESTCPP:%.cpp=%.o))
.SUFFIXES:
#
# Targets:
#
all: $(DEST)/$(TARGET) test
build/setup: ➁
-mkdir -p $(DEST)
-mkdir -p $(EXEDEST)
-mkdir -p $(OBJDEST)
-mkdir -p $(TESTOBJDEST)
date > $@
test: $(EXEDEST)/runtest ➂
$(EXEDEST)/runtest
$(DEST)/$(TARGET): $(OBJS) test build/setup ➃
ar rcs $@ $(OBJS)
ranlib $@
$(EXEDEST)/runtest: $(TESTOBJS) $(OBJS) ➄
$(LINK) -o $@ $(CPPFLAGS) $(TESTOBJS) $(OBJS) $(LFLAGS)
$(OBJDEST)/%.o: $(SRC)/%.cpp build/setup ➅
$(CXX) $(CXXFLAGS) -o $@ -c $<
$(TESTOBJDEST)/%.o: $(TESTSRC)/%.cpp build/setup ➆
$(CXX) $(CXXFLAGS) -o $@ -c $<
clean:
-rm -f build
➀ Symbolic names for directories, programs, and lists of files.
➁ Set up output directories
➂ Run the unit tests.
➃ Create the library from the .o
files generated by compilation
➄ Generate the executable for running unit tests.
➅ Compile the ADTs source code
➆ Comile the unit test source code.