Managing Projects with Make

make is a tool to manage builds, especially with multiple files. It has a rigid and peculiar syntax. It will look for a makefile first, followed by Makefile (on case-sensitive systems). The makefile defines one or more targets . The target is the product of one or more rules . The target is defined with a colon following its name. If there are dependencies those follow the colon. Dependencies are other files that are required to create the current target.

Targets and Rules

Example:

myexec:main.o module.o
<tab>g++ -o myexecmain.o module.o

The tab is required in the rule. Don’t ask why.

Macros (automatic targets) for rules:

$@ the file name of the current target
$< the name of the first prerequisite

Variables and Comments

We can define variables in makefiles:

CC =gcc
CXX=g++

We then refer to them as $(CC),$(CXX), etc. Common variables: F90, CC, CXX, FFLAGS, F90FLAGS, CFLAGS, CXXFLAGS, CPPFLAGS (for the preprocessor), LDFLAGS.

The continuation marker \ (backslash) can be used across multiple lines. It must be the last character on the line; do not add spaces after it.

Comments are indicated by the hash mark #. Anything beyond it will be ignored except for a continuation marker, which will extend the comment.

Suffix Rules

If all .cxx (or .cc or whatever) files are to be compiled the same way, we can write a suffix rule to handle them. It uses a phony target called .SUFFIXES.

.SUFFIXES: .cxx .o
	$(CXX) -c $(CXXFLAGS) –c $<

Pattern Rules

This is an extension by Gnu make (gmake), but nearly every make is gmake now. It is similar to suffix rules.
The pattern for creating the .o:

%.o: %.cxx
	$(CXX) $(CXXFLAGS) -c $<

Example:

PROG =	bmi

SRCS =	bmi.cxx bmistats.cxx stats.cxx

OBJS =	bmi.o bmistats.o stats.o

LIBS =	

CC = gcc
CXX = g++
CFLAGS = -O
#CXXFLAGS = -O -std=c++11
CXXFLAGS = -g -std=c++11
LDFLAGS = 
all: $(PROG)

$(PROG): $(OBJS)
	$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)

.PHONY: clean
clean:
	rm -f $(PROG) $(OBJS) *.mod

.SUFFIXES: $(SUFFIXES) .c .cpp .cxx

.c.o:
	$(CC) $(CFLAGS) -c $<

.cpp.o .cxx.o:
	$(CXX) $(CXXFLAGS) -c $<

bmi.o: bmi.cxx stats.h bmistats.h
bmistats.o: bmistats.cxx
stats.o: stats.cxx

In this example, notice that the suffix rule applies the global compiler flags and explicitly includes the -c option. If a particular file does not fit this pattern, a rule can be written for it and it will override the suffix rule. The link rule includes the loader flags and the -o flag. The compilation suffix rule uses the special symbol for the prequisite; the link rule applies to the current target.

The example also demonstrates switching back and forth between “debug” and “optimized” versions. The “debug” version would be created this time. The -g flag is required for debugging. The -C flag is a very useful flag specific to Fortran that enables bounds checking for arrays. Both these flags, but especially -C, will create a slower, sometimes much slower, executable, so when debugging is completed, always recompile with the optimization flag or flags enabled, of which -O is the minimum and will activate the compiler’s default level. You must always make clean anytime you change compiler options.

For further reading about make, see the gmake documentation.

Makemake

Makemake is a Perl script first developed by Michael Wester soon after the introduction of Fortran 90, in order to construct correct makefiles for modern Fortran code. The version supplied here has been extended to work for C and C++ codes as well. It is freely licensed but if you use it, please do not remove the credits at the top.

makemake

This version works reasonably well for Fortran, C, and C++. It will generate stubs for all languages. You may remove any you are not using. Also note that the output is a skeleton Makefile. You must at minimum name your executable, and you must fill in any other options and flags you wish to use. The makemake script blindly adds any files ending in the specified suffixes it finds in the current working directory whether they are independently compilable or not, so keep your files organized, and be sure to edit your Makefile if you have files you need but cannot be compiled individually.

Several other build tools, some called makemake, are available and may be newer and better supported. See here for example. That script also produces files for CMake, a popular build system, especially for Windows.

Building with an IDE and a Makefile

Several IDEs will manage multiple files as a “project” and will generate a Makefile automatically. They do not always pick up dependencies correctly, however, so the programmer may need to write a custom Makefile. A script like one of the makemake examples can help.

We will use the NetCDF library as an example. This is a popular library for self-describing data files. The example code is taken from their standard examples. The file are simple_xy_wr.cpp.

On our test Linux system, the library is not installed in a standard location, so we must add flags for the headers and library paths. Our example assumes the programmer added the environment variable $NETCDF_ROOT to the shell. First we run makemake to obtain a skeleton Makefile.

PROG =	

SRCS =	simple_xy_wr.cpp

OBJS =	simple_xy_wr.o

LIBS =	

CC = cc
CXX = c++
CFLAGS = -O
CXXFLAGS = -O
FC = f77
FFLAGS = -O
F90 = f90
F90FLAGS = -O
LDFLAGS = 
all: $(PROG)

$(PROG): $(OBJS)
	$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)

.PHONY: clean
clean:
	rm -f $(PROG) $(OBJS) *.mod

.SUFFIXES: $(SUFFIXES) .f .f90 .F90 .f95
.SUFFIXES: $(SUFFIXES) .c .cpp .cxx

.f90.o .f95.o .F90.o:
	$(F90) $(F90FLAGS) -c $<

.f.o:
	$(FC) $(FFLAGS) -c $<

.c.o:
	$(CC) $(CFLAGS) -c $<

.cpp.o .cxx.o:
	$(CXX) $(CXXFLAGS) -c $<

simple_xy_wr.o: simple_xy_wr.cpp

We edit it to add the addition information required and to remove unneeded lines.

PROG = simple_xy_wr

SRCS =	simple_xy_wr.cpp

OBJS =	simple_xy_wr.o

LIBS =	-lnetcdf_c++

CXX = c++
CXXFLAGS = -O
LDFLAGS = 
all: $(PROG)

$(PROG): $(OBJS)
	$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)

.PHONY: clean
clean:
	rm -f $(PROG) $(OBJS) *.mod

.SUFFIXES: $(SUFFIXES) .c .cpp .cxx

.cpp.o .cxx.o:
	$(CXX) $(CXXFLAGS) -c $<

simple_xy_wr.o: simple_xy_wr.cpp

Make with MinGW/MSYS2 on Windows

The MinGW64/MSYS2 system provides two versions of make. In newer releases, on newer Windows, either should work. If you do not wish to add an additional path to your PATH environment variable use mingw32-make. You can change the Geany build commands through its build tools menu. The mingw32-make tool may not support as many features as the full-fledged Gnu make provided by MSYS2. You can use Gnu make by adding the folder C:\msys64\usr\bin to your PATH variable. This would not require changing the build tool on Geany. To build a make project with Geany, be sure the main program tab is selected, then from the Build menu select Make.

Exercise 1: If you have not already done so, download or copy the example.cxx and its required adder.cxx and header adder.h. Place them into a separate folder. Run makemake. Edit the Makefile appropriately. Build the project using Geany or your choice of IDE.

Exercise 2: If you are working on a system with NetCDF available, download the two files and the completed Makefile into their own folder. Open Geany and browse to the location of the files. Open the two source files. Either select Make from the Build menu, or from the dropdown arrow next to the brick icon choose Make All. Build the code using the Makefile. Execute the result.

Previous
Next