Based on the make command and makefile file details

  • 2020-06-01 10:27:39
  • OfStack

1. Problems caused by multiple source files

When writing an c/c++ test program, we are used to changing the code one at a time and then compiling and running it right away to see the results of the run. This way of compiling is not a problem for small programs, but for large programs, because of the large number of source files, if you need to compile all the source files every time you change one place, this simple way of compiling all the source files directly is bad news for programmers.

Let's look at an example:


// main.c
#include "a.h"
// 2.c
#include "a.h"
#include "b.h"
// 3.c
#include "b.h"
#include "c.h"

If the programmer only modifies the header file c.h, the source files main.c and 2.c do not need to be compiled because they do not depend on the header file. For 3.c, since it contains c.h, the header file c.h must be newly compiled after the c.h has been changed.

If you change b.h and forget to compile 2.c, the resulting program may not work properly.

The make tool was created to solve this problem by recompiling all source files affected by the changes if necessary.

2. make command

The make command itself supports many options, the most common being the -f option. If we run it directly


make

The make command first looks for a file named makefile in the current directory, and if it cannot find it, looks for a file named Makefile.

To indicate which file the make command USES as an makefile file, use the -f option:


make -f Makefile1

3. makefile file

The makefile file was mentioned above, so what is the makefile file?

The make command is a powerful 10, but it doesn't understand how to build an application on its own. At this point, makefile comes out and tells make how the application is built. The combination of the make command and makefile files provides a powerful 10-point tool for managing projects, not only for controlling the compilation of source files, but also for other functions such as installing applications to target directories.

3.1 dependencies

Dependencies define the relationship between each file in the application and the other source files. For example, in the example above, we can define that the final application relies on the target files main.o, 2.o, and 3.o. Similarly, main.o depends on main.c and a.h, 2.o depends on 2.c, a.h and b.h, 3.o depends on 3.c, b.h and c.h.

In the makefile file, the dependencies are written as follows: the name of the target, followed by a colon, followed by a space or TAB tab, and finally a list of files separated by a space or TAB tab. The dependencies of the above example are as follows:


myapp: main.o 2.o 3.o
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h

This set of dependencies forms a hierarchy that shows the relationships between source files. For example, if the source files b.h change, you need to recompile 2.o and 3.o, and then you need to recompile myapp.

3.2 rules

The rules in the makefiel file define how the target is created. In the example above, we create 2.o using gcc-c 2.c. This gcc command is how target 2.o is created, also known as a rule.

In the makefile file, rules must begin with tab.

Create the Makefile1 file in the directory where the source file resides, as follows.


myapp: main.o 2.o 3.o
 gcc -o myapp main.o 2.o 3.o
main.o: main.c a.h
 gcc -c main.c
2.o: 2.c a.h b.h
 gcc -c 2.c
3.o: 3.c b.h c.h
 gcc -c 3.c

The three header files a.h, b.h, c.h are all empty. The source files are as follows:


/* main.c */
#include <stdlib.h>
#include "a.h"
extern void function_two();
extern void function_three();
int main()
{
 function_two();
 function_three();
 exit(EXIT_SUCCESS);
}

/* 2.c */
#include <stdio.h>
#include "a.h"
#include "b.h"
void function_two() {
 printf("function two\n");
}

/* 3.c */
#include <stdio.h>
#include "b.h"
#include "c.h"
void function_three() {
 printf("function three\n");
}

Execute make,


$ make -f Makefile1 
gcc -c main.c 
gcc -c 2.c 
gcc -c 3.c 
gcc -o myapp main.o 2.o 3.o

Run the application:


$ ./myapp 
function two 
function three

The output indicates that the application was built correctly.

If the b.h header file is changed, makefile can handle the 1 change correctly, only 2.c and 3.c can be recompiled:


make
0

3.3 annotations

The makefile file USES # for comments, and 1 continues until the end of this line.

3.4 the macro

Different platforms may use different compilers, and different environments (such as development and online environments) may use different compiler options. In order to modify the variable parameters of makefile, we can use macros to implement makefile.

The method that makefile references the macro definition is $(MACRONAME). Let's see how to use macros to overwrite the makefile file above.


make
1

It is customary to define the first target as all in the makefile file, and then list the other subordinate targets. makefile above also follows this convention.

Run the make command:


make
2

The application myapp was also built correctly.

3.5 multiple targets

The makefile file can define other targets in addition to the compiled target. For example, add an clean option to delete unwanted target files, add an install option to install a successfully compiled application into another directory, and so on.


all: myapp
CC = gcc
INSTDIR = /usr/local/bin
INCLUDE = .
CFLAGS = -g -Wall -ansi
myapp: main.o 2.o 3.o
 $(CC) -o myapp main.o 2.o 3.o
main.o: main.c a.h
 $(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.o: 2.c a.h b.h
 $(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
 $(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
clean:
 -rm main.o 2.o 3.o
install: myapp
 @if [ -d $(INSTDIR) ]; \
  then \
  cp myapp $(INSTDIR);\
  chmod a+x $(INSTDIR)/myapp;\
  chmod og-w $(INSTDIR)/myapp;\
  echo "Install in $(INSTDIR)";\
 else \
  echo "sorry, $(INSTDIR) does not exist";\
 fi

The makefile file above has a few caveats.

(1) the special target all only specifies the target myapp, so when the make command is executed, no target is specified, and its default behavior is to create the target myapp.

(2) target clean is used to test intermediate files generated during compilation.

(3) target install is used to install the application to the specified directory. It relies on myapp, that is, myapp must be created before install is executed. The install target consists of the shell script. Since the make command calls 1 shell when the rule is executed, and a new shell is used for each rule, you must add 1 \ at the end of each line of code above so that all shell scripts are on the same line.

The script begins with @, indicating that make does not display the command itself on standard output until these rules are executed.

Create myapp:


make
4

Install myapp to point to directory:


make
5

You can then directly execute myapp:


make
6

Delete intermediate files:


make
7

Related articles: