User Tools

Site Tools


guides:software:make:start

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:

  1. The user typed ls to show the files in the directory
  2. The user next typed make hello
    1. This caused make to compile hello.cpp and produce the output file hello.
  3. 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

  1. The user typed ls to show the contents of the directory
  2. The user typed make hello
    1. make built the executable hello from the source code hello.cpp
  3. The user typed make hello again
    1. Make observed that the time stamp on hello was newer than that on hello.cpp
    2. 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:

  1. The user types ls to show the contents of the directory
  2. The user types make bad to attempt to build an executable bad
    1. 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 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:

  1. The user types ls to show the contents of the directory.
    1. Note in this case, both hello.cpp and Makefile are present.
  2. The user types cat Makefile to display the contents of the Makefile.
  3. The user types make hello to build the executable
    1. 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:

  1. The user types ls to show the contents of the directory
  2. The user types cat Makefile to show the contents of the makefile.
    1. Notice that the line all: hello has been added.
  3. 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:

  1. The user types ls to show the contents of the directory
  2. The user types cat Makefile to show the contents of the Makefile
  3. The user types make to build the default target hello
  4. The user types ls to show that hello was constructed
  5. The user types make clean to remove project executable files.
  6. 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:

  1. There are no dependencies
  2. 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:

  1. The user types cat Makefile to show the contents of the Makefile
    1. Notice that the rule for clean has a space and not a tab.
  2. The user types make to build the project
    1. 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:

  1. The user types ls to show the contents of the directory
    1. Note that three independent source code files exist greeting.cpp, hello.cpp, and salute.cpp.
  2. The user types cat Makefile to show the contents of the makefile.
    1. The changes will be explained below.
  3. The user types make to build all primary targets.
    1. Make constructs three executables, hello, greeting, and salute.
  4. The user types ls to show the contents of the directory after the build.
  5. The user types make clean to clean up the project directory
    1. Make removes all of the executable files produced in the previous step.
  6. 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 <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;
      }
  • greeting.h
    • #ifndef GREETING
      #define GREETING
       
      std::string GreetingString(std::string language);
       
      #endif
  • greeting.cpp
    • #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;
      }
  • hello.cpp
    • #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;
      }
  • 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.

guides/software/make/start.txt · Last modified: 2022/08/02 11:59 by 127.0.0.1