First | Next | Previous | Last | Glossary | About

# Using Make - part 1

- Introduction
- The structure of a make file
- Targets other than files
- Using macros
- Using implicit rules
- A sample C++ project
- Tutorial 1
- Tutorial 2
- GNU Make : A Program for Directing Recompilation

## Introduction

Take a look at figure 1, you can't miss it. It is a chart of the files in a project and represents, in a graphical way, how the various components of an application relate to each other. The project represents a simple word-processor called myword.

Perhaps you have been working on this project for a couple weeks and after many hours of design work you have arrived at the structure of figure 1. myword, include, main, and lib all represent directories which contain the files you have carefully crafted.

The main directory contains the main files for the application. The lib directory contains source files which you have written and which contain functions used by the main files. The include directory contains a header file which is common to some or all of the other files.

To build this project you would have to compile each source file and them link them to build the final executable, for instance:

*** Build each object file ***
- g++ -c lib/lang.cc
- g++ -c lib/keyboard.cc
- g++ -c lib/screen.cc
- g++ -c lib/search.cc
- g++ -c main.cc
- g++ -c cmds.cc
- g++ -c helpw.cc
- g++ -c dict.cc

*** Link the object files to build the executable ***
- g++ -o myword.exe main.o cmds.o helpw.o dict.o lib/lang.o lib/keyboard.o lib/screen.o lib/search.o

*** Perhaps clean up the excess files ***
- rm *.o
- rm lib/*.o

*** Perhaps, if it works, install the executable to a place where everyone can get it ***
- cp myword.exe /usr/local/bin

If you made changes to a source file after it had all been built then you would have to rebuild to incorporate the changes, clean up again and install again. All this could get tedious and can also be error prone. What if you make an important change to search.cc, compile it and then get interrupted and forget to link the application?

Naturally there is a better way and that is to use make, a powerful program which will take care of all of these things for you.

    # a comment
    APPNAME = bldg
    TARGET  = $(APPNAME).exe
    OBJECTS = desc.o building.o $(APPNAME).o
    CC = g++

    $(TARGET): $(OBJECTS)
    	$(CC) -o $(APPNAME) $(OBJECTS)

    desc.o: desc.cc

    building.o: building.cc building.h declares.h

    $(APPNAME).o: $(APPNAME).cc building.h declares.h

    .PHONY: clean

    clean:
    	-rm *.o
    	-rm *.$$$$$$
    	strip $(TARGET)

Make uses a script file which contains rules on how to build an application. The script is usually called Makefile or makefile but can be given any name.

Here is a simple example of a makefile script which shows some of the features of make and its scripts.

The makefile contains:

- rules,
- targets,
- macros,
- dependencies and
- commands.

It can contain other things and can be a very simple makefile or a very complex makefile.

To use make you:

1. Create the makefile
2. run make in the directory where the makefile lives

Return to top of page

## The structure of a make file

## Rules

Makefiles contain rules which are composed of a target, a list of dependencies and a list of commands.

    target ... : dependencies ...
    	command
            ...
            ...

The target is usually the name of a file but can be some other kind of target. We see this later. The dependencies list is a list of the files that make up the target. For instance the source files, header files and library files. The list of commands is a list of command lines which will build the target or carry out some action like copying files or deleting files.

Note that the command line or lines are offset from the left by one TAB space. This is important! and I will mention it again.

## Example 1

The first example is a simple makefile which shows how we use the rules.

    menu1: menuex1.o menuex1res.o
    	g++ -o menu1 menuex1.o menuex1res.o -mwindows

    menuex1.o: menuex1.cc menuex1.h
    	g++ -c -w menuex1.cc

    menuex1res.o: menuex1res.rc menuex1res.h
    	windres menuex1res.rc menuex1res.o

There are three rules with the targets menu1:, menuex1.o: and menuex1res.o:. Note that each target starts in the leftmost column and is terminated by a colon.

A target is a file name which results from some command action and is followed by its dependencies:

    menu1: menuex1.o menuex1res.o

This states that menu1 depends on the object files menuex1.o and menuex1res.o

A target has a command for building purposes, like

    menu1: menuex1.o menuex1res.o
    	g++ -o menu1 menuex1.o menuex1res.o -mwindows

This states that to build the target menu1 we need to execute the command line

    	g++ -o menu1 menuex1.o menuex1res.o -mwindows

NOTA BENE: there is a TAB before the command line

The target, dependencies and the command constitute a MAKE RULE.

If menu1 doesn't exist then the command will be executed and menu1 will be created. If either menuex1.o or menuex1res.o doesn't exist then the appropriate rule is invoked to create the missing target. Make does more than this though. If a target dependency changes then the rule is invoked so that the target is rebuilt. A change in a dependency is obvious from the file date/time stamp. If the target is older than one of its dependents then the target needs to be rebuilt.

Make starts with the first rule unless directed to some other rule.

Make command lines
- make
  - starts with the menu1 target
- make menuex1.o
  - starts with the menu1ex1 target
- make menuex1res.o
  - starts with the menu1ex1res target

Return to top of page

## Targets other than files

Often we want to direct make to do something other than create a target, for example delete some files, or copy some files.

We can use non-file targets to do this.

## Example 2

You can see that the clean target has no dependencies and has a simple command.

    # Makefile1

    menu1: menuex1.o menuex1res.o
    	g++ -o menu1 menuex1.o menuex1res.o -mwindows

    menuex1.o: menuex1.cc menuex1.h
    	g++ -c -w menuex1.cc

    menuex1res.o: menuex1res.rc menuex1res.h
    	windres menuex1res.rc menuex1res.o

    clean:
    	rm *.o

This is used like this:

    make clean

NB This Makefile is called Makefile1 and is called like this

    make -f Makefile1 clean

Make normally looks for a file called Makefile or makefile but can be directed to look for some other file by using the -f switch. The target is shown on the command line. The make command above uses Makefile1 and generates the target - clean.

There is a problem with using targets that don't have any dependencies. If there is a file called clean then, since a target is a file, make will see no dependencies, report that clean is up to date and NOT execute the command.

## PHONY

To get around the problem we can use the directive PHONY. This directive allows us to list a number of targets that are not files.

    # Makefile2
    menu1: menuex1.o menuex1res.o
    	g++ -o menu1 menuex1.o menuex1res.o -mwindows

    menuex1.o: menuex1.cc menuex1.h
    	g++ -c -w menuex1.cc

    menuex1res.o: menuex1res.rc menuex1res.h
    	windres menuex1res.rc menuex1res.o

    .PHONY : clean

    clean:
    	rm *.o

.PHONY is an example of a special target which states that there will be a target of the given name, in this case clean, but it is not to be treated as a file target. The action for make is to execute the command.

Return to top of page

## Using macros

As your projects grow and the number of targets and dependencies increases it can become quite tedious maintaining the makefile. To overcome the tedium and lessen the possibility of errors we can use macros to represent the different things we use in the makefile.

## Example 3

A makefile macro has the form:

    MACRONAME = string

used as $(MACRONAME) or ${MACRONAME} unless the macro is a single character in which case we can use $MACRONAME. Like a macro definition for CPP, int make the macro is expanded as it occurs in the makefile.

    # Makefile3
    OBJECTS = menuex1.o menuex1res.o
    RESCOMP = windres.exe
    CC = g++

    menu1: $(OBJECTS)
    	$(CC) -o menu1 ${OBJECTS} -mwindows

    menuex1.o: menuex1.cc menuex1.h
    	$(CC) -c -w menuex1.cc

    menuex1res.o: menuex1res.rc menuex1res.h
    	$(RESCOMP) menuex1res.rc menuex1res.o

    .PHONY : clean

    clean:
    	echo cleaning up
    	rm $(OBJECTS)
    	rm *.$$$$$$

In this example you can see how useful macros are. If we need to add another object file we can just add it to the list of objects which is assigned to the macro OBJECTS. We don't have to find every line that contains object files and modify it.

You can see to that the compiler is defined as a macro.

A NOTE: I have been using PFE which generates backup files with the extension $$$. I also use a DOS version of the rm command to delete files. The $ symbol has special meaning for make, as you'll see later, so to use a $ symbol we need to use $$.

Return to top of page

## Using implicit rules

## Example 3

Many of the compile and link operations are quite repetitive and quite simple, for instance to generate an object file:

    $(CC) -c menuex1.cc

We can use implied rules to do these sort of things. Such rules are built into make.

    # Makefile4
    OBJECTS = menuex1.o menuex1res.o
    RESCOMP = windres.exe
    CC = g++

    menu1: $(OBJECTS)
    	$(CC) -o menu1 ${OBJECTS} -mwindows

    menuex1.o: menuex1.h

    menuex1res.o: menuex1res.rc menuex1res.h
    	$(RESCOMP) menuex1res.rc menuex1res.o
    ...

You can see that the line which builds target menuex1.o doesn't have a command. There is however an inbuilt command. If we want and object file from a C or C++ source file then it implies that we will run the compiler and give it the -c switch.

Return to top of page

## A sample C++ project

## Building a "building" project

This simple project consists of three C++ source files and two header files. If you have done the lessons on inheritance you will probably recognise them.

    APPNAME = bldg
    TARGET  = $(APPNAME).exe
    OBJECTS = desc.o building.o $(APPNAME).o
    CC = g++

    $(TARGET): $(OBJECTS)
    	$(CC) -o $(APPNAME) $(OBJECTS)

    desc.o: desc.cc

    building.o: building.cc building.h declares.h

    $(APPNAME).o: $(APPNAME).cc building.h declares.h

    .PHONY: clean

    clean:
    	-rm *.o
    	-rm *.$$$$$$
    	strip $(TARGET)

You can see that there is nothing in the makefile you haven't seen before except for the dash in front of rm.

The dash in front of rm is supposed to suppress error messages. For example if there were .o files then -rm is supposed to suppress the error messages it would normally emit. It seems not to work under DOS but that is probably me misusing some feature again. You can use rm -f to force rm to ignore non-existent files.

The source files follow below.

A NOTE ON THE SOURCE CODE: If some of the source puzzles you, particularly the comments on compilation units in the building.cc file, then you should read the lesson on compilation units.

bldg.cc

    /*
      This example uses an array of domestic objects which are not
      initialised.
      It demonstrates the need to define the default constructor for
      domestic. It is already declared in the domestic class definition.
    */

    #include <iostream>
    #include "building.h"
    #include "declares.h"

    extern const char * RoofDesc[];
    extern const char * ConstructionDesc[];
    extern const char * CommercialDesc[];

    int main()
    {
     commercial skyscraper(FLAT, CONCRETE, 120, OFFICE);
     domestic home(PITCHED, CLAYBRICK, 1, 8, 3, 1);

     domestic estate[5];

     home.inspect();
     skyscraper.inspect();

     return 1;
    }

desc.cc

    const char * RoofDesc[]          = {"flat", "pitched", "skillion"};
    const char * ConstructionDesc[]  = {"stone", "claybrick", "steel", "wooden"};
    const char * CommercialDesc[]    = { "office", "factory", "shop", "garage"};

declares.h

    /*
     Declarations for the building project
    */
    #ifndef _DECLARES_H_
    #define _DECLARES_H_

    #include <iostream>

    #define MINFLOORS 1

    typedef enum rooftype         {FLAT, PITCHED, SKILLION};
    typedef enum constructiontype {STONE, CLAYBRICK, STEEL, WOODEN,
                                   CONCRETE, MUDBRICK};
    typedef enum commercialtype   {OFFICE, FACTORY, SHOP};

    #endif

building.h

    #ifndef _BUILDING_H_
    #define _BUILDING_H_

    #include <iostream>
    #include "declares.h"

    #define MINFLOORS 1

    //A simple abstract class. We don't normally directly access this class
    //it will be used by the derived classes.

    class building
    {
      public:
       building();
       building(rooftype, constructiontype, int);
       void inspect();
      protected:
       rooftype roof;
       constructiontype walls;
       int floors;
      private:
    };

    class commercial : public building
    {
      public:
       commercial();
       commercial(rooftype, constructiontype, int, commercialtype);
       void inspect();
      protected:
       commercialtype usage;
    };

    class domestic : public building
    {
      public:
       domestic();
       domestic(rooftype, constructiontype, int, int, int, int);
       void inspect();
      protected:
       int allrooms,
           bedrooms,
           bathrooms;
    };

    #endif

building.cc

    #include "building.h"

    /*
     These are used by both this file, which is one compilation unit, and
     bldg.cc, which is another compilation unit.
     We tell each unit that the identifiers are available externally.
    */

    extern const char * RoofDesc[];
    extern const char * ConstructionDesc[];
    extern const char * CommercialDesc[];

    building::building()
    {
      cout << "Don't interrupt, I'm building a big estate." << endl;
    }

    building::building(rooftype r, constructiontype w, int f)
    { roof = r;
      walls = w;
      if (f > 0)
       floors = f;
      else
       floors = MINFLOORS;
    }

    void building::inspect()
    {
      cout << RoofDesc[roof] << ","
           << ConstructionDesc[walls] << ","
           << floors << " floors, ";
    }

    commercial::commercial(rooftype r, constructiontype w, int f,
                           commercialtype u
                          ) : building(r, w, f)
    { usage = u;
    }

    void commercial::inspect()
    {
      building::inspect();
      cout << CommercialDesc[usage]
           << endl;
    }

    domestic::domestic()
    {

    }

    void domestic::inspect()
    {
      building::inspect();
      cout << allrooms << " rooms, with "
           << bedrooms << " bedrooms and "
           << bathrooms << " bathrooms."
           << endl;
    }

    domestic::domestic(rooftype r, constructiontype w, int f,
                       int a, int be, int ba
                      ) : building(r, w, f)
    { allrooms = a;
      bedrooms = be;
      bathrooms = ba;
    }

Return to top of page

## Tutorial 1

Exercise 1.1

Copy all of the building project files, including the makefile and try the example to make sure it works.

Exercise 1.2

Create a function of your own that does anything you like, display the time for instance. Make sure that it uses a header file of its own and any other header files it needs. Now add it to your makefile and call the function from main().

Return to top of page

## Tutorial 2

Most of the example makefiles were based on sources that you use in this tutorial. The source files follow the tutorial. NOTE: This is a windows program so don't try to build it in Linux. If you haven't completed the windows programming sessions then you might like to skip this tutorial.

Exercise 2.1

Get copies of Makefile4 and the following source files and see if you can get it all to build.

Return to top of page

menuex1.h

    #define CM_FILE_EXIT   	9001
    #define CM_FILE_OPEN	9002
    #define CM_FILE_CLOSE	9003
    #define CM_CHOICE1   	9010
    #define CM_CHOICE2   	9011
    #define CM_CHOICE3   	9012
    #define CM_CHOICE4   	9020
    #define CM_CHOICE5   	9022
    #define CM_CHOICE6   	9023

menuex1res.cc

    #include <windows.h>
    #include "menuex1.h"
    #include "menuex1res.h"

    LRESULT CALLBACK WndProc( HWND hwnd, UINT Message, WPARAM wParam,
                              LPARAM lParam);

    int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        LPSTR lpCmdLine, int nCmdShow)
    {  static char 		szClassName[] = "MenuEx1WindowClass";
       WNDCLASSEX		WndClass;
       HWND 		hwnd;
       MSG 			Msg;

       WndClass.cbSize        = sizeof(WNDCLASSEX);
       WndClass.style         = CS_HREDRAW | CS_VREDRAW;
       WndClass.lpfnWndProc   = WndProc;
       WndClass.cbClsExtra    = 0;
       WndClass.cbWndExtra    = 0;
       WndClass.hInstance     = hInstance;
       WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
       WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
       WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
       WndClass.lpszMenuName  = "MENUEX1";
       WndClass.lpszClassName = szClassName;
       WndClass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

       if(!RegisterClassEx(&WndClass))
       { MessageBox(0, "Window Registration Failed!", "Error!",
                    MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL);
          return 0;
       }

       hwnd = CreateWindowEx(
          WS_EX_CLIENTEDGE,
          szClassName,
          TITLE_STRING,
          WS_OVERLAPPEDWINDOW,
          CW_USEDEFAULT,
          CW_USEDEFAULT,
          320,
          180,
          NULL,
          NULL,
          hInstance,
          NULL);

       if(hwnd == NULL)
       { MessageBox(0, "Window Creation Failed!", "Error!",
                    MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL);
         return 0;
       }

       ShowWindow(hwnd, nCmdShow);
       UpdateWindow(hwnd);

       while(GetMessage(&Msg, NULL, 0, 0))
       { TranslateMessage(&Msg);
         DispatchMessage(&Msg);
       }
       return Msg.wParam;
    }

    LRESULT CALLBACK WndProc( HWND hwnd, UINT Message, WPARAM wParam,
                              LPARAM lParam)
    { switch(Message)
       { case WM_COMMAND:
          switch(LOWORD(wParam))
           { case CM_FILE_EXIT:
              PostMessage(hwnd, WM_CLOSE, 0, 0);
              break;
             case CM_CHOICE1:
              MessageBox(hwnd, "Choice 1", "Menu 2",
                         MB_OK);
              break;
             case CM_CHOICE2:
              MessageBox(hwnd, "Choice 2", "Menu 2",
                         MB_OK | MB_ICONINFORMATION);
              break;
             case CM_CHOICE3:
              MessageBox(hwnd, "Choice 3", "Menu 2",
                         MB_OK | MB_ICONEXCLAMATION);
              break;
             case CM_CHOICE4:
              MessageBox(hwnd, "Choice 4", "Menu 3",
                         MB_ABORTRETRYIGNORE | MB_ICONERROR);
              break;
             case CM_CHOICE5:
              MessageBox(hwnd, "Choice 5", "Menu 3",
                         MB_OK | MB_YESNO | MB_ICONSTOP);
              break;
             case CM_CHOICE6:
              MessageBox(hwnd, "Choice 6", "Menu 3",
                         MB_YESNOCANCEL | MB_ICONSTOP );
              break;
           }
          break;
         case WM_CLOSE:
          DestroyWindow(hwnd);
          break;
         case WM_DESTROY:
          PostQuitMessage(0);
          break;
         default:
          return DefWindowProc(hwnd, Message, wParam, lParam);
       }
       return 0;
    }

menuex1res.rc

    #include <windows.h>
    #include "menuex1res.h"
    #include "menuex1.h"

    MENUEX1 MENU
    {
       POPUP "&File"
       { MENUITEM "File &Open",  CM_FILE_OPEN
         MENUITEM "File &Close", CM_FILE_CLOSE
         MENUITEM "E&xit",       CM_FILE_EXIT
       }
       POPUP "Menu &2"
       { MENUITEM "Choice &1", CM_CHOICE1
         MENUITEM "Choice &2", CM_CHOICE2
         MENUITEM "Choice &3", CM_CHOICE3
       }
       POPUP "Menu &3"
       { MENUITEM "Choice &4", CM_CHOICE4
         MENUITEM "Choice &5", CM_CHOICE5
         MENUITEM "Choice &6", CM_CHOICE6
       }
    }

menuex1res.h

    #define TITLE_STRING "This is the window title - (DB 1999)"

First | Next | Previous | Last | Glossary | About

Copyright © 1999 - 2001 David Beech