Make is a build automation tool that is designed to automatically construct executable programs from source code.
Make simplifies the build process for projects at all levels.
Make has a set of built in rules that allow you to compile single file programs with no additional configuration. The only requirement is that you provide the correct extension to your source code file. Make uses this extension to decide which compiler to run.
Make recognizes files ending with .cpp, .C and several other extensions as c++ source code. To build an executable from a file with an appropriate extension type make progname. In the following example, the user wishes to construct the executable hello from the program hello.cpp.
$ ls hello.cpp $ make hello g++ hello.cpp -o hello $ ls hello* hello.cpp
In the example:
Make will only rebuild an executable if it is necessary. Make looks at the timesamp on the source files and executable to determine if such a rebuild is required. If the executable is newer than the source code, make will not rebuild the executable.
$ ls hello.cpp $ make hello g++ hello.cpp -o hello $ make hello make: 'hello' is up to date.
In this example
If the user wished to rebuild hello in the above example they could do one of several things:
Unless directed otherwise, make uses the files in the current directory when attempting to build an executable. In the next example, the user attempts to build an executable when source code is missing.
$ ls hello* hello.cpp $ make bad make: *** No rule to make target 'bad'. Stop.
In this example:
Make uses a file called either Makefile or makefile as a configuration file. If one of these two files are present, make will read the this file and use it to determine how to build the executable. In general Makefile is the preferred name for this file.
A relatively powerful yet simple use of a Makefile is to provide the proper command line arguments to build your executable.
Assume that your instructor wishes you to compile your program with the following compiler flags:
-g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion
You could
The last option is fairly easy. Simple edit Makefile with your favorite editor and add the following line:
CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion
CXXFLAGS is a variable that is used to tell make what compiler flags should be used to compile c++ programs.
$ ls Makefile hello.cpp $ cat Makefile CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion $ make hello g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion hello.cpp -o hello $ ls Makefile hello* hello.cpp
In this example:
A second easy use of the makefile is to specify what you wish to build when you type make. In all of the examples so far, the user has typed make hello. This is acceptable while you are working on a project but becomes problematic when you return to a project after some time, or a new to a project. In this case, you might not remember to type make hello.
To solve this problem make allows you to specify default targets. A default target will be constructed when the user types make.
A common way to specify a default target is to declare a rule to build the target all. Make will use the first target appearing in the makefile as the default target. In the following example, such a rule has been added to the makefile.
$ ls Makefile hello.cpp $ cat Makefile CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion all: hello $ make g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion hello.cpp -o hello
In the example:
In the example above, all is a target. Since it is the first target, make will attempt to build this target when no target is specified. The target all is a standard target for make. You could call this anything, but it is traditional to call the default target all.
A target consists of a target name, in this case all and a dependency list. The dependency list consists of a single dependency hello. This tells make that if you want to build everything, you need to build hello. Make uses the default rules to build hello.
Another standard target is clean. Usually makefiles are constructed so that when the user types make clean all executable files are removed from the directory.
$ ls Makefile hello.cpp $ cat Makefile CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion all: hello clean: rm -f hello $ make g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion hello.cpp -o hello $ ls Makefile hello* hello.cpp $ make clean rm -f hello $ ls Makefile hello.cpp
In the example:
The Makefile contains the following:
CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion all: hello clean: rm -f hello
Note that an additional target, clean, has been added to the Makefile. This target is different from the default target for all in two ways:
The rule is rm -f hello. This is a shell command that will be executed when make attempts to build the target. In this case, the result is to remove the file hello. The -f flag says to force the removal of the file, but in this case the purpose is to make rm ignore non-existent files. This keeps make from reporting an error if hello is not present.
Note that when constructing rules in make, the rules need to be indented by a tab. The following example demonstrates what would happen if rm -f hello were not preceded by a tab.
$ cat Makefile CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion all: hello clean: rm -f hello $ make Makefile:6: *** missing separator. Stop.
In this example:
It is extremely important that all rules for a target begin with a tab.
$ ls Makefile greeting.cpp hello.cpp salute.cpp $ cat Makefile CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion OBJS = hello greeting salute all: ${OBJS} clean: rm -f ${OBJS} $ make g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion hello.cpp -o hello g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion greeting.cpp -o greeting g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion salute.cpp -o salute $ ls Makefile greeting* greeting.cpp hello* hello.cpp salute* salute.cpp $ make clean rm -f hello greeting salute $ ls Makefile greeting.cpp hello.cpp salute.cpp
In this example:
The makefile has been modified on three lines.
First, a variable called OBJS has been added. This contains the name of all of the programs that should become part of the default target. By declaring this variable, more programs can be added to the default target by adding their name to the value of this variable.
OBJS = hello greeting salute
Secondly, the default target has been changed. Instead of listing a single target, or even multiple targets, the variable OBJS is specified. Make will replace this variable with the values stored in the variable. The variable is specified as ${OBJS} in this case. Don't worry about the syntax, just copy it. As you learn more about shell programming this syntax will be easier to understand.
all: ${OBJS}
Finally, the rule to make clean has been altered to remove all of the files listed in the variable OBJS.
clean: rm -f ${OBJS}
The final makefile for this section is:
CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion OBJS = hello greeting salute all: ${OBJS} clean: rm -f ${OBJS}
One of the most powerful uses of make is to compile multiple files into a single executable.
For this example the code is decomposed into the following files:
#ifndef STRING_TOOLS #define STRING_TOOLS void LowerCase(std::string & word); #endif
include <iostream> #include <cstdlib> // gives access to tolower #include "stringTools.h" using namespace std; void LowerCase(string & word){ for(auto &x : word) { x = static_cast<char> (tolower(static_cast<int> (x))); } return; }
#ifndef GREETING #define GREETING std::string GreetingString(std::string language); #endif
#include <iostream> #include "greeting.h" #include "stringTools.h" using namespace std; string GreetingString(string language){ string greeting = "Hello World"; LowerCase(language); if (language == "pig") { greeting = "Ellohay Orldway"; } else if (language == "ubbi") { greeting = "Hubellubo Wuborublubdub"; } return greeting; }
#include <iostream> #include "greeting.h" using namespace std; int main() { string languages[] = {"PIG", "piG","UBBI", ""}; for (auto lang: languages) { cout << " The greeting in \"" << lang << "\"" << endl; cout << GreetingString(lang) << "!" << endl; } return 0; }
CXXFLAGS = -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wu ninitialized -Wshadow -Wconversion -std=c++14 OBJS = hello all: ${OBJS} hello: greeting.o stringTools.o greeting.o: greeting.h stringTools.h stringTools.o: stringTools.h clean: rm -f ${OBJS} *.o
To build this code, you are required to compile stringTools.cpp into stringTools.o, greeting.cpp into greeting.o and finally build hello from hello.cpp, stringTools.o and greeting.o.
This could be accomplished by hand by executing the following
g++ -c greeting.cpp g++ -c stringTools.cpp g++ -o hello hello.cpp stringTools.o greeting.o
Of course, if you have command line flags, this becomes much more complex. In addition, you need to remember to recompile ALL of the proper files when any of the files change, this includes the header files. Using make simplifies this process.
Using the above Makefile the code can be built with one command
$ make g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -std=c++14 -c -o greeting.o greeting.cpp g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -std=c++14 -c -o stringTools.o stringTools.cpp g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -std=c++14 hello.cpp greeting.o stringTools.o -o hello $ ls Makefile greeting.h hello* stringTools.cpp stringTools.o greeting.cpp greeting.o hello.cpp stringTools.h
Note that all components are compiled and the final executable is produced. If any of the component files change, make will recompile only the parts necessary to rebuild the entire project. For example, if greeting.cpp were change, that file, along with hello.cpp would need to be recomipled, but stringTools.o would not need to be rebuilt.
$ touch greeting.cpp $ make g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -std=c++14 -c -o greeting.o greeting.cpp g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -std=c++14 hello.cpp greeting.o stringTools.o -o hello
In order to build this project using make, you need to inform make of the dependencies built into the project. In this case, to build hello, we need to have both greeting.o and stringTools.o because the code in hello calls code in both of these files. The following line in the makefile performs this task
hello: greeting.o stringTools.o
You do not need to tell make that hello depends on hello.cpp, that is built into the system.
You also do not need to tell make that hello depends on greeting.h or stringTools.h. This is accomplished by the next set of instructions in the makefile.
stringTools.o: stringTools.h
This line tells make that stringTools.o depends on stringTools.h. Make does not have default rules for header files, so this line is necessary. The result of this line is if stringTools.h changes, stringTools.o will need to be rebuilt. If this happens, make is able to deduce that hello will need to be rebuilt as well. This is good since hello.cpp includes stringTools.h.
greeting.o: greeting.h stringTools.h
This line tells make that greeting.o depends on both greeting.h and stringTools.h. Again greeting.o also depends on greeting.cpp, but make has this rule built in.