Translate

Chủ Nhật, 4 tháng 12, 2016

Make

Created by ThangVu

1. Overview

The make utility automatically determines which pieces of a large program need to be recompiled, and issues commands to recompile them.
We need a file called a makefile to tell make how to compile and link a program.

A makefile is executed when we type "make" in command line, if we has more than one makefile in current directory, we can use "-f name" option to select the makefile to be executed.
A make file contains:
  • Rules
  • Variables
  • Directives
  • Comments

2. Rules in makefile

2.1 Shape of a rule

Fig. 2.1. The rule shape
Fig. 2.1 show a rule that consists of:
  • A target: is usually the name of a file that if generated by a program, or the name of an action to carry out (such as "clean")
  • A prerequisite: is a file that is used as input to create the a\target. A target often depends on several files.
  • A recipe is an action that make carries out. A recipe may have more than one command, the recipe lines start with a tab character (which can be modified by .RECIPEPREFIX variable)
A simple example of makefile.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
edit :  main.o kbd.o command.o display.o \
  insert.o search.o files.o utils.o
  cc -o edit main.o kbd.o command.o display.o \
  insert.o search.o files.o utils.o
main.o : main.c defs.h
 cc -c main.c
kbd.o : kbd.c defs.h command.h
 cc -c kbd.c
command.o : command.c defs.h command.h
 cc -c command.c
display.o : display.c defs.h buffer.h
 cc -c display.c
insert.o : insert.c defs.h buffer.h
 cc -c insert.c
search.o : search.c defs.h buffer.h
 cc -c search.c
files.o : files.c defs.h buffer.h command.h
 cc -c files.c
utils.o : utils.c defs.h
 cc -c utils.c
clean :
 rm edit main.o kbd.o command.o display.o \
 insert.o search.o files.o utils.o
When this makefile is executed, the first target, which is the default target, is invoked (we can modify the default target with .DEFAULT_GOAL variable. Its recipe is executed when any prerequisites is update, which will be done by checking timestamp of prerequisites. We can check the timestamp of a file by command "stat filename".

2.2. Phony target

A target is not always a filename, it can be a action, for example clean target:
?
1
2
clean:
 rm *.o test
If there is no file named clean, the recipe will be executed unconditionally if we type make clean . However, if a file named clean in the current directory, this file always is considered up to date and the recipe will not be executed.

To avoid this problem, we use .PHONY target:

?
1
2
3
.PHONY: clean
clean:
 rm *.o test
Once this is done, make clean will run the recipe regardless of whether there is a file named clean.

2.3. Multiple targets in a Rule

A rule with multiple targets is equivalent to writing many rules, each with one target and all identical aside from that. 

For example;
?
1
2
test1.o test2.o : test.h
  @echo $@ depends on $<
is equivalent to:

?
1
2
3
4
test1.o : test.h
 @echo test1.o depends on test.h
test2.o
 @echo test2.o depends on test.h

2.4. Multiple rules for one target

One file can be the target of several rules. All the prerequisites mentioned in all the rules are merged into one list of prerequisites for the target. Of the target is older than any prerequisite from any rule, the recipe is executed.
For example:

?
1
2
3
foo.o : foo.c
bar.o : bar.c
foo.o bar.o : def.h

2.5. Static pattern rules

Static pattern rules are rules which specify multiple targets and construct the prerequisite names for each target based on the target name.
Here is the syntax of a static pattern rule:


  • The targets  list specifies the targets that the rule applies to.
  • The target-pattern and preque-patterns say how to compute the prerequisites for each target. 
For example:

?
1
2
3
4
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
 $(CC) -c $(CFLAGS) $< -o $@
This example will substitute foo.o bar.o by foo.c bar.c.

2.6. Implicit rules

If the rule for recipe is not specified, make will figure out which implicit rule to use based on which kind of source file exists or can be made.
For example:

?
1
2
test : foo.o bar.o
 gcc -o test foo.o bar.o
We mention foo.o and bar.o but do not give a rule for it, make will automatically look for an implicit rule that tells how to update it. Each implicit rule has a target pattern and prerequisite patterns. If we have a file foo.c, make will run C compiler to build foo.o.

2.7. Type of prerequisites

There are two different types of prerequisites in GNU make:
  • normal prerequisites
  • order-only prerequisites

2.8 Double colon rule

If a target appears in different rule and they are double-colon, each of them is independent of the others. Each double-colon rule's recipe is executed if the target is older than any prerequisites of that rule.
For example:
    test.o :: foo.o
     @echo this is the first rule
    test.o :: bar.o
     @echo this is the second rule

    If the foo.o and/or bar.o is updated, the first and/or the second recipe is also executed.

    2.7.1. Normal prerequisites

      A normal prerequisite makes to statements:
    • Impose an order in which recipes will be invoked: the recipes for all prerequisites of a target will be completed before the recipe for the target is run.
    • if any prerequisite newer than the target, the target is considered out-of-date and must be rebuilt. 


    2.7.2. Order-only prerequisites

     A order-only prerequisite imposes that the target will not be updated because the timestamp of the order-only prerequisite is changed. We can add prerequisites by using '|' charater. The normal prerequisites are in the left of '|', and the order-only ones are in the right.
     For example:
    ?
    1
    test.o : test.c | file.c

    If we modify test.c then make will rebuild test.o, however if we modify file.c then test.o will not be rebuilt.

    It is helpful in the following case.

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    folder/test.o : test.c | folder
     touch folder/test.o
    folder:
     mkdir folder
    add:
     touch folder/addFile.c
    remove:
     rm folder/addFile.c
    A folder is consider to be modified if a file is added, removed or rename. Some time we dont want to update folder/test.o when folder is modified by adding or removing some other files. To do that we declare folder as a order-only prerequisite.

    3. Recipes in rules

    Recipes are meant to be interpreted by the shell and so they are written using shell syntax.

    3.1. Recipe echoing

    Normally make prints each line of the recipe before it is executed (echoing). When a line starts with '@', the echoing of that line is suppressed. 
    For example:
    ?
    1
    2
    all:
     @echo this is a make file
    The output will be:
       this is a make file
    If the example is written without '@' at the beginning of the recipe, the output will be
       echo this is a make file
       this is a make file

    3.2. Recipe execution

     When it is time to execute recipes to update a target, they are executed by invoking a new sub-shell for each line of the recipe, unless the .ONESHELL special target is in effect (provided in version 3.82). So if we call ls after cd, the result may be not expected.
     A solution is using && operator or .ONESHELL target:

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    all:
     cd folder && ls
     
    #or
    .ONESHELL:
    all:
     cd folder
     ls

    3.3. Errors in Recipes

    After each shell invocation returns, make looks at its exit status. If the shell completed successfully (the exit status is zero), the next line in the recipe í executed in a new shell.
    If there is an error, make gives up on the current rule, and perhaps on all rules. Some time the failure of a certain recipe line does not indicate a problem. For example we may use mkdir command to ensure that a directory exists.
    To ignore erors in a recipe line, write a '-' at the beginning of the recipes.
    For example:

    ?
    1
    2
    clean:
     -rm -f *.o
    This enables make continue even rm cannot remove a file
    3.4. Interrupting or Killing make
    If make gets a fatal signal while a shell is executing, it may delete the target file that the recipe was supposed to update. The purpose of deleting the target is to make sure that is remade from scratch when make is next run.

    4. Variables in makefile

    A variable is a name defined in a makefile to represent a string of text. A variable name may be any sequence of characters not containing ':', '#', '=', or white space. 

    4.1. Basic variable references

    To get a variable's value, write a dollar sign followed by the name of the variable in parentheses or braces. Either $(foo) or ${foo} is valid reference to the variable foo
    Variable references can be used in any context: targets, prerequisites, recipes, directives, and new variable values. 
    Variable references work by strict textual substitution. 
    For example:
    ?
    1
    2
    foo = c
    prog.o : prog.$(foo)
    ?
    1
    $(foo)$(foo) - $(foo) prog.$(foo)

    4.2. Flavors of Variable

    Two ways that a variable in GNU make can have a value.

    • Recursive expansion
    • Simple expansion

    4.2.1. Recursive expansion

    It is defined by using '=', if it contains references to other variables, these references are expanded whenever this variable is substituted.
    For example:
    ?
    1
    2
    3
    4
    foo = $(goo)
    goo = $(bar)
    bar = something
    all:;echo $(foo)
    will echo "something".
    - Advantage: we can write
    ?
    1
    2
    CFLAGS = $(include_dirs) -O
    include_dirs = -Ifoo -Ibar
    - Disavantage: we cannot write
    ?
    1
    CFLAGS = $(CFLAGS) -O
    because it will cause an infinite loop in the variable expansion.

    4.2.2. Simple expansion

    Simply expanded variables are defined by lines using ':=' or '::=' (both are equivalent). Simply expanded variables do not contain any references to other variables, it contains their values as of the time this variable was defines.
    Therefore
    ?
    1
    2
    3
    x := foo
    y := $(x) bar
    x := later
    is equivalent to
    ?
    1
    2
    y := foo bar
    x := later

    4.3. How variables get their values


    • Specify an overriding value when running make
    • Specify a value in the make file with an assignment or with a verbatim definition
    • Variables in the environment become make variables.
    • Several automatic variables are given new values for each rule. Each of these has a single conventional use
    • Several variable have constant initial values.

    4.4. Setting variables.

    Beside using '=', ':=' or ':=', if we would like a variable to beset to a value only if it's not already set, then we can use '?=' instead of '='.
    Following examples are equivalent.

    ?
    1
    foo ?= bar
    and
    ?
    1
    2
    3
    ifeq ($(origin foo), undefined)
     foo = bar
    endif

    We can append more text into variable by using '+=' operator.
    For example:
    variable := value
    variable += more
    is equivalent to
    variable := value
    variable := $(variable) more

    4.5. Automatic variables


    • $@: Name of whichever target caused the rule's command to be run
    • $%: If target name is archive,
    • $<: The name of the first prerequisite
    • $?: The names of all the prerequisites that newer than the target.
    • $^: The name of all the prerequisites.
    • $+: This is like '$^',  but prerequisites listed more than once are duplicated in order they were listed in makefile
    • $*: The name of the dependency, but except extension (like .c, .o)
    • $|: The names of all the order-only prerequisites.

    5. Function call syntax

     A function call resembles a variable reference. A function call looks like this: 

    ?
    1
    $(function argument)
    For example: 
    ?
    1
    2
    3
    foo = a.o b.o c.o
    bar := $(subst .o,.c,$(foo))
    #bar is now a.c b.c c.c

    The subst function replace .o pattern by .c pattern in $(foo).

    6. Practice

    We have 3 files: hellomake.c, hellofunc.c, and hellomake.h as follows

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //hellomake.c
    #include "hellomake.h"
     
    int main() {
      // call a function in another file
      myPrintHelloMake();
     
      return(0);
    }
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //hellofunc.c
    #include <stdio .h="">
    #include <hellomake .h="">
     
    void myPrintHelloMake(void) {
     
      printf("Hello makefiles!\n");
     
      return;
    }
    </hellomake></stdio>
    ?
    1
    2
    3
    4
    5
    6
    //hellomake.h
    /*
    example include file
    */
     
    void myPrintHelloMake(void);
    6.1. Makefile1
    ?
    1
    2
    hellomake: hellomake.c hellofunc.c
      gcc -o hellomake hellomake.c hellofunc.c -I.
    This is simple but not efficient in terms of compiling only the latest changes.
    6.2. Makefile2

    ?
    1
    2
    3
    4
    5
    CC=gcc
    CFLAGS=-I.
     
    hellomake: hellomake.o hellofunc.o
         $(CC) -o hellomake hellomake.o hellofunc.o $(CFLAGS)
    CC and CFLAGS are some defined constants to tell mae how we want to compile the file hellomake.c and hellofunc.c. By putting the object file (.o) in the dependency list, make knows how it must first compile the .c versions individually, and then build the executable hellomake.

    This is the most small scale projects. However, the is one thing missing: dependency on the include files, make would not recompile the .c file even though they needed to be.
    6.3. Makefile 3
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    CC=gcc
    CFLAGS=-I.
    DEPS = hellomake.h
     
    %.o: %.c $(DEPS)
     $(CC) -c -o $@ $< $(CFLAGS)
     
    hellomake: hellomake.o hellofunc.o
     gcc -o hellomake hellomake.o hellofunc.o $(CFLAGS)
    This create the macro DEPS, which is the set of .h files on which the .c files depend.
    6.4. Makefile 4
    If we want to use $^ and $@ macros, we define a variable OJB as in makefile 4.
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CC=gcc
    CFLAGS=-I.
    DEPS = hellomake.h
    OBJ = hellomake.o hellofunc.o
     
    %.o: %.c $(DEPS)
     $(CC) -c -o $@ $< $(CFLAGS)
     
    hellomake: $(OBJ)
     gcc -o $@ $^ $(CFLAGS)
    6.5. Makefile5
    If we want our project is well-organized.
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    CC=gcc
    CFLAGS=-I$(IDIR)
    IDIR=../include
    BINDIR=../bin
    ODIR=../obj
    SRCDIR=../src
     
    _DEPS=hellomake.h
    DEPS=$(patsubst %,$(IDIR)/%,$(_DEPS))
     
    _OBJS=hellomake.o hellofunc.o
    OBJS=$(patsubst %,$(ODIR)/%,$(_OBJS))
     
    $(BINDIR)/hellomake : $(OBJS)
     $(CC) -o $@ $^ $(CFLAGS)
     
    $(OBJS): | $(ODIR)  $(BINDIR)
     
    $(ODIR):
     -mkdir $(ODIR)
    $(BINDIR):
     -mkdir $(BINDIR)
     
    $(ODIR)/%.o : $(SRCDIR)/%.c $(DEPS)
     $(CC) -c -o $@ $< $(CFLAGS)
     
    .PHONY : clean
    clean:
     -rm -rf $(ODIR) $(BINDIR)

    References

    http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/
    GNU make

    Không có nhận xét nào:

    Đăng nhận xét