Skip to content

Make / Overview

This documentation will be expanded as examples are created.

The make program is used to control how software is compiled. It is an older technology that was developed for C and other languages, and is still an effective tool. The basic usage is to create a file named makefile and then run the make command, which will detect and use the makefile. The makefile includes instructions for controlling a workflow, typically to compile software, but it can be used to perform other tasks.

See the following resources:

Sections on this page:


Technical Topics

The following sections provide information about specific technical topics that are useful.

Printing Usage

It is helpful to print the makefile targets so that it is clear what can be done by the makefile. It may be appropriate to make the help the default target so that a different target must be specified. This can avoid confusion by requiring a target rather than just running with make.

For example, add a target as the default. See the source file in GitHub.

default: help

Then create a target that prints the usage when make or make help are run:

# help: print the targets that are available and useful configuration information
help:
        @echo "--------------------------------------------------------------------------------"
        @echo "StateMod makefile targets:"
        @echo ""
        @echo "all        Default target that prints this help message."
        @echo "clean      Remove dynamically created files (but not final executable)."
        @echo "help       Print this message."
        @echo "statemod   Compile the StateMod executable, recompiling any .o if .for modified."
        @echo "veryclean  Make the 'clean' target, and also remove the final executable."
        @echo ""
        @echo "file.o     Compile the source file.for file into object file file.o,"
        @echo "           useful to check syntax for a single file."
        @echo "--------------------------------------------------------------------------------"
        @echo "Important makefile variables that are used:"
        @echo ""
        @echo "FC (compiler) = $(FC)"
        @echo "STATEMOD_VERSION (from statem.for) = $(STATEMOD_VERSION)"
        @echo "--------------------------------------------------------------------------------"
        @echo "Important environment variables that are used:"
        @echo ""
        @echo "OS (to determine operating system) = $(OS)"
        @echo "--------------------------------------------------------------------------------"

Setting Variable to Program Output

It is often useful to set a variable to the output of a program. One example is to detect the version of a program being compiled so the version can be used in output files, such as the executable program name. The important syntax is to use the := $(shell ...) syntax to assign the standard output from a program (or sequence of programs) to a variable. See the source file in GitHub.

STATEMOD_VERSION := $(shell cat statem.for | grep 'ver =' | grep -v 'xx' | cut -d '=' -f 2 | sed "s/'//g" | tr -d ' ' )

Customizing Makefile for Operating System

It is helpful to ensure that a makefile is portable across operating systems, or at least for the target operating systems. This allows a single makefile to be used and maintained. The operating system may be a clear distinction (e.g., Linux and Windows) or a more nuanced distinction (e.g., Cygwin and Git Bash, which are Linux-like but run on top of Windows).

Detecting the operating system on Linux could be done by using the uname program, for example search uname output for CYGWIN and if found assume that the operating system is Cygwin. However, this will not work if the operating system on which make is being run does not have a uname program. Therefore, it may be necessary to try a hierarchy of checks to determine the operating system.

The Windows operating system defines the environment variable OS that indicates the operating system, for example:

C:\>echo %OS%
Windows_NT

The value of this environment variable may not be important to the make command but the existence of the variable is a good indicator that the operating system is Windows. Therefore, logic like the following can be used in the makefile, in this case to specify different source files for each operating system, in this case subroutines that deal with paths (slashes, colons, etc.). See the source file in GitHub.

ifdef OS
        # Assume Windows legacy naming convention
        getpath_o_file = getpath.o
        putpath_o_file = putpath.o
else
        # Assume Linux
        getpath_o_file = getpath_linux.o
        putpath_o_file = putpath_linux.o
endif

Alternate approaches can be taken to achieve the same result. The above file list would then be included in the dependency list as follows:

some-program: \
        statem.o \
                namext.o \
                $(parse_o_file) \
                $(getpath_o_file) \

Similarly, the operating system check can be used to control the output executable name:

ifdef OS
        # Windows...
        $(FC) $(FFLAGS) -o statemod-$(STATEMOD_VERSION)-gfortran-32bit.exe $^ $(LDFLAGS)
        @echo "-----------------------------------------------------------------------"
        @echo "Executable is statemod-$(STATEMOD_VERSION)-gfortran-32bit.exe"
        @echo "-----------------------------------------------------------------------"
else
        # Linux...
        $(FC) $(FFLAGS) -o statemod-$(STATEMOD_VERSION)-gfortran-32bit $^ $(LDFLAGS)
        @echo "-----------------------------------------------------------------------"
        @echo "Executable is statemod-$(STATEMOD_VERSION)-gfortran-32bit"
        @echo "-----------------------------------------------------------------------"
endif

Customizing Makefile for Compiler

Similar to how a makefile can be customized for the operating system, it is possible to customize the makefile for a compiler. The ifeq syntax shown below compares the value of the FC variable to the literal string gfortran and if equal the instructions in first clause are used. The following example illustrates how the compiler name controls files that should be included. See the source file in GitHub.

# Compiler-specific routines to handle command line parsing and date/time processing
ifeq ($(FC),gfortran)
        # gfortran code version
        parse_o_file = parse_gfortran.o
        dattim_o_file = dattim_gfortran.o
else
        # Legacy Lahey code version
        parse_o_file = parse.o
        dattim_o_file = dattim.o
endif