Makefiles... the bane of my existence
Table of Contents
The examples range from a simple one directory program to a multi-directory, recursively compiled program. While the example makefiles are for a Java program, they can be easily adapted to other languages.
Makefiles are commonly used, especially in the Unix/Linux world. Java developers, however, rarely uses make. They tend to use Ant, Maven, Gradle, or an IDE's build system.
The documentation for GNU is
<http://www.gnu.org/software/make/manual/make.html>
The following two free online books by O'Reilly are also very helpful.
Here are more open and free books generously provided by O'Reilly.
Example Programs C vs Java
The Java programming language is similar to C. Below is a C and a Java
program.
#include
A Makefile contains a list of rules. The first line of each rule
has a target followed by a list of prerequesites. Next comes a
sequence of commands that generate the target from the prerequisites.
These commands are referred to as the recipe.
In this example one directory contains three java source files, and the Makefile
is used to compile each .java into a .class.
The following is an example of a Makefile.
The make command runs the Makefile, and the argument, all, selects
the target. When a target's rule is run, the prerequisites
are run first followed the recipe.
So in this example the all target@apos;s prequesites are run from left
to right, going through Simple2.class, Simple3.class and Simple1.class. As
shown below, the make all command runs the javac compiler on the
three Java source files, resulting in the three class files.
When make all is run again it returns Nothing to be done because the
timestamp of the prerequisites is newer than the timestamp of the source
file prerequisites. In Step 4, after the Simple3.java timestamp
is updated using the touch command (typically a file's timestamp is
updated by editing the file), the make all command runs just the recipe
for target Simple2.class, but the recipes for the other targets are
not run.
In step 6 the make clean deletes all the targets. Makefiles often
have a clean target. The rule (line 8 above) does not have any
prerequisites which means the recipe will always run.
In step 11 the make command without a target causes
the Makefile's first target to run. By convention the all
target is usually the first target.
The following is a command lines that run a makefile.
This second Makefile simplifies the first Makefile by using
the
Automatic Variables
Automatic variable
Automatic variable
In the above example we defined targets (the i**.class** files). In this
final example we use
makefile variables
to define the .java source file prerequisites, and we use a
makefile substitution reference
to create a list of target objects (.class files).
This following is a simple makefile example to compile a multi-file Java program.
Recursive Makefiles are used to build programs when the source files are in
more than one directory.
This example has four source directories. Each of the source directories
contains a similar makefile.
The sub-directories needed by the directory are identified in subblocks
target. In this example the subblocks rule runs the srcb and srcc
makefiles.
Here is a makefile for directory 'srca'
Makefile for Directory 'srca', which then calls make files in sub-directories.
In this example the srcb Makefile runs the srcd Makefile.
Note, when all the sub-directory Makefiles are run the classpath
has been pre-defined by the first makefile, so all the resulting .class
files get stored in same directory.
Makefile for sub-directory 'srcb&apos'
Makefile for sub-directory 'srcc'
Makefile for sub-dirctory 'srcd'
Here are results of the recursive Makefile. It's worth noting that srcd's Makefile is called twice
(because both srcb and srcc use it) and, as expected, the second call results in 'Nothing to be done'. Also note that
all the resulting .class files are in the same directory, as expected. And when the Makefile is run
a second time, all the Makefiles return 'Nothing to be done'.
In the above recursive Makefile example each source file that needs
to be compiled will be compile... individually... one... at... a... time. So if
a lot of files need to be re-compiled (such as after a make clean), it
may take longer... much longer. In this second recursive example
the Makefile recursively builds a filelist, identifies which files need to
be recompiled, and submit those files as a group to the javac compiler.
In this example, three files are used. Makefile.common is a shared file
that each directory's Makefile calls. Each directory has an
identical Makefile that has just one line to call the Makefile.common.
Each directory also as a unique filelist.mk that defines the
directory's source files and related sub-blocks. Here are the
three files in text for easy download or cut-n-paste.
Here is the source code for Makefile.common.
Line 1 above defines the PRJ variable if it has not been previously defined.
The Makefile ?= Conditional Variable Assignment operator
assigns PRJ only if PRJ is undefined. I expect PRJ to be a predefined
environment variable.
Line 2 defines the TESTNAME variable if it has not been previously defined.
This variable is only needed for the run target and should be defined
on the make command line make run TESTNAME=Test1).
Lines 4-5 define MK_CLASSPATH and MK_LOGPATH. The .class files
generated by javac will be stored in MK_CLASSPATH. The filelist
and test run log files will be stored in MK_LOGPATH.
Lines 7-8 define the compiler and compiler options.
Line 10 includes
the directory's filelist.mk. As shown below, filelist.mk
recursively includes its sub-directories, and appends its files to
the filelist variable. After the include finishes its recursive walk
through the sub-directories, filelist contains a list of all the source
files.
Line 12 defines the
phony targets.
Lines 13-15 define the compile rule. This rule is basically just an easy
to type alias for 'log/filelist.log'.
Lines 17-21 defined the filelist.log rule. This rule makes the classpath
and log directories if they don't already exist. Next the files that
have changed (indicated by the
Automatic Variable $?
are compiled by javac. Finally, this run write the list of files to
filelist.log.
Here is a makefile for each directory.
The above Makefile is not required for sub-directories. It's just a
one-liner to call Makefile.common.
The following is an example of a filelist, filelist.mk, that can be
included in a makefile.
Many Markdown variations exist. The original Markdown was developed by
Jon Gruber and
Aaron Swartz.
Github uses
Github Flavored Markdown (GFM).
Ghost.org uses an enhanced version of
Markdown.
MultiMarkdown
supports many addition features.
Emphasis is usually rendered as Italic and is supported in Markdown
converting
The
The highlight feature converts
The superscript, raise-to-the-power shortcut such as x2 and xabc converts
Two underscores for bold. One underscore for emphasis.
Three underscores for the once depricated underline, and now for the
revived Unarticulated Annotation Element, which is almost always rendered as
an underline.
The end.
Simple Java Makefile
all: Simple2.class Simple3.class Simple1.class
Simple1.class: Simple1.java
	javac -d . -classpath . Simple1.java
Simple2.class: Simple2.java
	javac -d . -classpath . Simple2.java
Simple3.class: Simple3.java
	javac -d . -classpath . Simple3.java
clean:
	rm -f *.class
[1]$ make all
javac -d . -classpath . Simple2.java
javac -d . -classpath . Simple3.java
javac -d . -classpath . Simple1.java
[2]$ ls .class
Simple1.class Simple2.class Simple3.class
[3]$ make all
make: Nothing to be done for 'all'.
[4]$ touch Simple3.java
[5]$ make
javac -d . -classpath . Simple3.java
[6]$ make clean
rm *.class
[7]$ make
javac -d . -classpath . Simple2.java
javac -d . -classpath . Simple3.java
javac -d . -classpath . Simple1.java
[8]$ make
make: Nothing to be done for 'all'.
[9]$ make clean
rm *.class
[10]$ ls *.class
ls: cannot access .class: No such file or directory
[11]$ make
javac -d . -classpath . Simple2.java
javac -d . -classpath . Simple3.java
javac -d . -classpath . Simple1.java
Simplified Makefile for Java
%
and $<
.
%
is the root of the target. In the following example, when the target
is Simple2.class, the root is Simple2.
$<
is the name of a target's first prerequisite.
In the following example, when the target is all the first prerequisite
is Simple2.class and when the target is Simple2.clas the first
prerequisie is Simple2.class.
all: Simple2.class Simple3.class Simple1.class
	echo "First prerequesite is" $<
%.class: %.java
	javac -d . -classpath . $<
clean:
	rm -f *.class
Makefile for Java Example
sourcefiles = \
Simple3.java \
Simple2.java \
Simple1.java
classfiles = $(sourcefiles:.java=.class)
#classfiles = Simple3.class Simple2.class Simple1.class
all: $(classfiles)
%.class: %.java
	javac -d . -classpath . $<
clean:
	rm -f *.class
Recursive Makefile for Java Example
# =================================================================
# File: srca/Makefile
sourcefiles = \
Simple3a.java \
Simple2a.java \
Simple1a.java
classfiles = $(patsubst %.java,$(classpath)/%.class,$(sourcefiles))
ifndef classpath
export classpath = $(PWD)/class
endif
.PHONY: all clean subblocks
all: subblocks $(classfiles)
subblocks:
	cd ../srcb; make
	cd ../srcc; make
$(classpath)/%.class: %.java
	@mkdir -p $(classpath)
	javac -d $(classpath) -classpath $(classpath) $<
clean:
	rm -f $(classpath)/*.class
# =================================================================
# File: srcb/Makefile
sourcefiles = \
Simple3b.java \
Simple2b.java \
Simple1b.java
classfiles = $(patsubst %.java,$(classpath)/%.class,$(sourcefiles))
ifndef classpath
export classpath = $(PWD)/class
endif
.PHONY: all clean subblocks
all: subblocks $(classfiles)
subblocks:
	cd ../srcd; make
$(classpath)/%.class: %.java
	@mkdir -p $(classpath)
	javac -d $(classpath) -classpath $(classpath) $<
clean:
	rm -f $(classpath)/*.class
# =================================================================
# File: srcc/Makefile
sourcefiles = \
Simple3c.java \
Simple2c.java \
Simple1c.java
classfiles = $(patsubst %.java,$(classpath)/%.class,$(sourcefiles))
ifndef classpath
export classpath = $(PWD)/class
endif
.PHONY: all clean subblocks
all: subblocks $(classfiles)
subblocks:
	cd ../srcd; make
$(classpath)/%.class: %.java
	@mkdir -p $(classpath)
	javac -d $(classpath) -classpath $(classpath) $<
clean:
	rm -f $(classpath)/*.class
# =================================================================
# File: srcd/Makefile
sourcefiles = \
Simple3d.java \
Simple2d.java \
Simple1d.java
classfiles = $(patsubst %.java,$(classpath)/%.class,$(sourcefiles))
ifndef classpath
export classpath = $(PWD)/class
endif
classfiles = $(patsubst %.java,$(classpath)/%.class,$(sourcefiles))
.PHONY: all clean subblocks
all: subblocks $(classfiles)
subblocks:
$(classpath)/%.class: %.java
	@mkdir -p $(classpath)
	javac -d $(classpath) -classpath $(classpath) $<
clean:
	rm -f $(classpath)/*.class
[localhost srca]$ make
cd ../srcb; make
make[1]: Entering directory '/home/roq/Documents/java/sj/complex/srcb'
cd ../srcd; make
make[2]: Entering directory '/home/roq/Documents/java/sj/complex/srcd'
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple3d.java
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple2d.java
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple1d.java
make[2]: Leaving directory '/home/roq/Documents/java/sj/complex/srcd'
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple3b.java
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple2b.java
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple1b.java
make[1]: Leaving directory '/home/roq/Documents/java/sj/complex/srcb'
cd ../srcc; make
make[1]: Entering directory '/home/roq/Documents/java/sj/complex/srcc'
cd ../srcd; make
make[2]: Entering directory '/home/roq/Documents/java/sj/complex/srcd'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/home/roq/Documents/java/sj/complex/srcd'
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple3c.java
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple2c.java
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple1c.java
make[1]: Leaving directory '/home/roq/Documents/java/sj/complex/srcc'
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple3a.java
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple2a.java
javac -d /home/roq/Documents/java/sj/complex/srca/class -classpath /home/roq/Documents/java/sj/complex/srca/class Simple1a.java
[roq@localhost srca]$ ls ./class
Simple1a.class Simple1c.class Simple2a.class Simple2c.class Simple3a.class Simple3c.class
Simple1b.class Simple1d.class Simple2b.class Simple2d.class Simple3b.class Simple3d.class
[localhost srca]$ make
cd ../srcb; make
make[1]: Entering directory '/home/roq/Documents/java/sj/complex/srcb'
cd ../srcd; make
make[2]: Entering directory '/home/roq/Documents/java/sj/complex/srcd'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/home/roq/Documents/java/sj/complex/srcd'
make[1]: Leaving directory '/home/roq/Documents/java/sj/complex/srcb'
cd ../srcc; make
make[1]: Entering directory '/home/roq/Documents/java/sj/complex/srcc'
cd ../srcd; make
make[2]: Entering directory '/home/roq/Documents/java/sj/complex/srcd'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/home/roq/Documents/java/sj/complex/srcd'
make[1]: Leaving directory '/home/roq/Documents/java/sj/complex/srcc'
Faster Recursive Makefile for Java
PRJ ?= PRJ_NOT_DEFINED
TESTNAME ?= TESTNAME_NOT_DEFINED
MK_CLASSPATH ?= ./class
MK_LOGPATH ?= ./log
COMPILER := javac
COMPILE_OPTIONS := -d $(MK_CLASSPATH) -classpath $(MK_CLASSPATH)
include ./filelist.mk
.PHONY: compile run clean
compile: $(MK_LOGPATH)/filelist.log
	@echo "Compile complete. Filelist is '$(MK_LOGPATH)/filelist.log'."
	@echo "To run test: make run TESTNAME=Testx"
$(MK_LOGPATH)/filelist.log: $(filelist)
	@mkdir -p $(MK_CLASSPATH) $(MK_LOGPATH)
	$(COMPILER) $(COMPILE_OPTIONS) $?
	@echo $ |sed 's# #\n#g' > $(MK_LOGPATH)/filelist.log
$(MK_LOGPATH)/$(TESTNAME).log: $(MK_LOGPATH)/filelist.log
	java -classpath $(MK_CLASSPATH) $(TESTNAME) |tee $(MK_LOGPATH)/$(TESTNAME).log
run: $(MK_LOGPATH)/$(TESTNAME).log
	@echo "Results are in '$(MK_LOGPATH)/$(TESTNAME).log'."
	@echo "Before re-running 'rm $(MK_LOGPATH)/$(TESTNAME).log' or 'make clean'"
clean:
	rm -f $(MK_LOGPATH)/*.log $(MK_CLASSPATH)/*.class
include $(PRJ)/common/Makefile.common
include $(PRJ)/OtherStuff1/src/filelist.mk
include $(PRJ)/OtherStuff2/src/filelist.mk
include $(PRJ)/OtherStuff3/src/filelist.mk
filelist +=
$(PRJ)/example/src/File1.java \
$(PRJ)/example/src/File2.java
Common Makefile Mistakes
# ===============================================================
# Makefile - identical for each directory
#
include $(PRJ)/common/Makefile.common
# ===============================================================
# filelist.mk - unique for each directory
include $(PRJ)/OtherStuff1/src/filelist.mk
include $(PRJ)/OtherStuff2/src/filelist.mk
filelist += \
$(PRJ)/example/src/File1.java \
$(PRJ)/example/src/File2Ram.java
# ===============================================================
# Makefile.common - called by all Makefiles
PRJ ?= PRJ_NOT_DEFINED
TESTNAME ?= TESTNAME_NOT_DEFINED
MK_CLASSPATH ?= ./class
MK_LOGPATH ?= ./log
COMPILER := javac
COMPILE_OPTIONS := -d $(MK_CLASSPATH) -classpath $(MK_CLASSPATH)
include ./filelist.mk
.PHONY: compile run clean
compile: $(MK_LOGPATH)/filelist.log
	@echo "Compile complete. Filelist is '$(MK_LOGPATH)/filelist.log'. To run test: make run TESTNAME=Testx"
$(MK_LOGPATH)/filelist.log: $(filelist)
	@mkdir -p $(MK_CLASSPATH) $(MK_LOGPATH)
	$(COMPILER) $(COMPILE_OPTIONS) $?
	@echo $ |sed 's# #\n#g' > $(MK_LOGPATH)/filelist.log
$(MK_LOGPATH)/$(TESTNAME).log: $(MK_LOGPATH)/filelist.log
	java -classpath $(MK_CLASSPATH) $(TESTNAME) |tee $(MK_LOGPATH)/$(TESTNAME).log
run: $(MK_LOGPATH)/$(TESTNAME).log
	@echo "Results are in '$(MK_LOGPATH)/$(TESTNAME).log'. Before re-running 'rm $(MK_LOGPATH)/$(TESTNAME).log' or 'make clean'"
clean:
	rm -f $(MK_LOGPATH)/*.log $(MK_CLASSPATH)/*.class
Markdown Extensions
Escape
*text*
to <em>text</em>
. You can block this
conversion by with the backslash \*
as *shown here*. You can
also use the html code *
which *displays an asterisk*.
Strikethrough
strikethrough feature converts ~~text~~
to <s>text</s>
.
==text==
to <mark>text</mark>
.
x^2 and x^abc^
to
x<sup>text</sup> and x<sup>abc</sup>
.
___text___
is
converted to <u>text</u>
and renders as
text.