leader

Makefile Examples

img/intro-img.jpg

View markdown

Makefiles... the bane of my existence
Or... perhaps more like a boon. Makefiles automate complexity. While I dread writing them, I marvel when they work. This page provides a few makefile examples that range from simple to complex. I don't write makefiles often enough to stay fluent. I hope that these examples will help future me, and possibly you, create effective, efficient makefiles. A word of advice before we continue. When writing makefiles, as with most human endevours, the Keep It Simple principle is always recommended.

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.

  1. Open Books
  2. Free Books

Example Programs C vs Java

The Java programming language is similar to C. Below is a C and a Java program. #include int main() { printf("hello\n"); } public class HelloWorld { public static void main(String[] args) { // Prints "Hello, World" to the terminal window. System.out.println("Hello, World"); } }

Simple Java Makefile

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.


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

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.


 [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

This second Makefile simplifies the first Makefile by using the Automatic Variables % and $<.

Automatic variable % is the root of the target. In the following example, when the target is Simple2.class, the root is Simple2.

Automatic variable $< 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

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.


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

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.


# =================================================================
# 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

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'


# =================================================================
# 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

Makefile for sub-directory 'srcc'


# =================================================================
# 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

Makefile for sub-dirctory 'srcd'


# =================================================================
# 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

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'.


 [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

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.


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

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.


 include $(PRJ)/common/Makefile.common

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.


 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

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.

Escape

Emphasis is usually rendered as Italic and is supported in Markdown converting *text* to <em>text</em>. You can block this conversion by with the backslash \* as *shown here*. You can also use the html code &ast; which *displays an asterisk*.

Strikethrough

The strikethrough feature converts ~~text~~ to <s>text</s>.

The highlight feature converts ==text== to <mark>text</mark>.

The superscript, raise-to-the-power shortcut such as x2 and xabc converts x^2 and x^abc^ to x<sup>text</sup> and x<sup>abc</sup>.

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. ___text___ is converted to <u>text</u> and renders as text.

The end.