====== Make ======
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.
===== Simple Use =====
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.
==== A simple Example ====
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:
- The user typed //ls// to show the files in the directory
- The user next typed //make hello//
- This caused make to compile hello.cpp and produce the output file //hello//.
- The user typed //ls// again to show that the file //hello// had been produced.
==== Make Only Rebuilds when Necessary ====
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
- The user typed //ls// to show the contents of the directory
- The user typed //make hello//
- make built the executable //hello// from the source code //hello.cpp//
- The user typed //make hello// again
- Make observed that the time stamp on //hello// was newer than that on //hello.cpp//
- Based on that observation, make decided that there was no need to rebuild //hello//
If the user wished to rebuild //hello// in the above example they could do one of several things:
* Remove the executable hello (//rm hello//)
* Change the timestamp on hello.cpp (//touch hello.cpp//)
* Edit the file hello.cpp and save the new file.
==== Make Can Not Build an Unknown Target ====
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:
- The user types //ls// to show the contents of the directory
- The user types //make bad// to attempt to build an executable //bad//
- Make does not detect a source code file that can be used to construct bad, so it reports an error and exits.
===== Setting Compiler Flags for Make =====
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.
==== Using a Makefile to Provide Compiler Flags ====
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 [[guides:software:gcc:start|compiler flags]]:
-g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion
You could
* Memorize these flags and type them every time you compile.
* Type in these flags once for each session and use command line history to recompile each time.
* Write a shell script to compile your programs for you.
* Create a //Makefile// that instructs make to use these flags.
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:
- The user types //ls// to show the contents of the directory.
- Note in this case, both hello.cpp and Makefile are present.
- The user types //cat Makefile// to display the contents of the //Makefile//.
- The user types //make hello// to build the executable
- Make uses the contents of the //Makefile// to construct //hello// using the desired compiler flags
==== Specifying a Single Target in a Makefile ====
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:
- The user types //ls// to show the contents of the directory
- The user types //cat Makefile// to show the contents of the makefile.
- Notice that the line //all: hello// has been added.
- The user types //make// to build the project
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.
==== Adding a Rule to Remove Executable Files and Other Compiler Generated Files ====
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 user types //ls// to show the contents of the directory
- The user types //cat Makefile// to show the contents of the Makefile
- The user types //make// to build the default target //hello//
- The user types //ls// to show that hello was constructed
- The user types //make clean// to remove project executable files.
- The user types //ls// to show that the executable files have been removed.
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:
- There are no dependencies
- There is a //rule// to be applied when building the target.
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:
- The user types //cat Makefile// to show the contents of the Makefile
- Notice that the rule for //clean// has a space and not a tab.
- The user types //make// to build the project
- Make reports an error because it can not parse the Makefile due to the missing tab.
It is extremely important that all rules for a target begin with a tab.
===== Building Multiple Targets =====
$ 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 user types //ls// to show the contents of the directory
- Note that three independent source code files exist //greeting.cpp, hello.cpp, and salute.cpp//.
- The user types //cat Makefile// to show the contents of the makefile.
- The changes will be explained below.
- The user types //make// to build all primary targets.
- Make constructs three executables, //hello, greeting//, and //salute//.
- The user types //ls// to show the contents of the directory after the build.
- The user types //make clean// to clean up the project directory
- Make removes all of the executable files produced in the previous step.
- The user types //ls// to show the contents of the directory.
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}
===== Makefiles for Multiple File Compilation =====
One of the most powerful uses of make is to compile multiple files into a single executable.
==== Example ====
For this example the code is decomposed into the following files:
* //stringTools.h// defines a method for converting a string to lowercase
*
#ifndef STRING_TOOLS
#define STRING_TOOLS
void LowerCase(std::string & word);
#endif
* //stringTools.cpp// implements the code in stringTools.h
*
include
#include // gives access to tolower
#include "stringTools.h"
using namespace std;
void LowerCase(string & word){
for(auto &x : word) {
x = static_cast (tolower(static_cast (x)));
}
return;
}
* //greeting.h//
*
#ifndef GREETING
#define GREETING
std::string GreetingString(std::string language);
#endif
* //greeting.cpp//
*
#include
#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;
}
* //hello.cpp//
*
#include
#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;
}
* //Makefile//
*
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
==== The Makefile ====
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.