你带酒来,我有故事

makefile学习笔记

:: 代码生涯 二十画生 1147℃ 0评论

The syntax of Makefiles(makefile的语法):

   makefile主要包括两个方面:1.一组依赖关系  2.规则

一个简单的makefile文件:Makefile1

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 -o 3.c

1.一组依赖关系:

依赖关系定义了最终应用程序里的每个文件 与源文件之间的关系。比如上面最终应用程序(可执行程序myapp)依赖于文件main.o,2.o 和3.o;同理main.o依赖于main.c和a.h。

依赖关系的写法:先写目标的名称,然后紧跟一个冒号,接着是空格或制表符tab,最后是用空格或制表符tab隔开的文件列表(这些文件用于创建目标文件)。如上面的:myapp: main.o 2.o 3.o

2.规则

  规则定义了目标的创建方式。如上面的:  gcc -o myapp main.o 2.o 3.o

这里有一个非常奇怪而又令人遗憾的语法现象:空格与制表符tab是有区别的,规则所在的行必须以制表符tab开头,用空格是不行的。如果少了制表符tab,make命令就不会正常工作,会提醒你少了间隔(制表符tab)。

3.make makefile

  如果makefile文件不是默认文件名makefile、Makefile或GNUmakefile时,在调用make命令时加上-f选项。

现在来具体尝试创建makefile文件:

首先创建三个头文件(为简单全部为空文件):

touch a.h

touch b.h

touch c.h

然后创建c文件:

main.c如下:

/* main.c */
#include <stdlib.h>
#include "a.h"

extern void function_two();
extern void function_three();

int main()
{
        function_two();
        fucntion_three();
        exit(EXIT_SUCCESS);
}

2.c如下:

/* 2.c */
#include <stdio.h>
#include "a.h"
#include "b.h"

void function_two()
{
        printf("Hello ");
}

3.c如下:

/* 3.c */
#include <stdio.h>
#include "b.h"
#include "c.h"

void function_three()
{
        printf("World\n");
}

现在来执行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

此时生成了一个叫myapp的可执行文件,运行myapp

./myapp

运行结果:Hello World

假如更新了b.h,再次make会发生什么不同,

touch b.h

make -f Makefile1

会执行如下命令:

  gcc -c 2.c

  gcc -c 3.c

  gcc -o myapp main.o 2.o 3.o

可见依赖b.h的目标全进行了重新编译,而不依赖它的main.o却不需要重新编译,就也是make的优势所在。

 

makefile文件中的注释

  makefile文件中的注释以#号开关,一直延续一这一行的结束。

 

makefile文件中的宏

  makefile文件中的宏相当于C中的#define,好处不言而喻。

在makefile中定义宏:MACRONAME = value

引用宏:$(MACRONAME)或${MACRONAME},如果想把一个宏的值设置为空,可令等号(=)后面留空。

A Makefile with Macors:Makefile2

all: myapp

# Which compiler
CC = gcc

# Where are include file kept
INCLUDE = .

# Options for development
CFLAGS =  - g -Wall -ansi

# Options for release
# CFLAGS = -O -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

现在删除旧的安装文件,并通过这个新的makefile文件创建新安装文件:

  rm *.o myapp

  make -f Makefile2

会执行如下命令:

  gcc -I. -g -Wall -ansi -c main.c
  gcc -I. -g -Wall -ansi -c 2.c
  gcc -I. -g -Wall -ansi -c 3.c
  gcc -o myapp main.o 2.o 3.o

运行可执行文件myapp

./myapp

运行结果:Hello World

make命令将$(CC)、$(CFLAGS)和$(INCLUDE)替换为相应的宏定义。

其中all处可以指定多个目标(即可产生多个可执行文件),比如:all: myapp1 myapp2。

 

伪目标:

伪目标并不是一个文件,即并不会产生如myapp的可执行文件,它只是一个标签,所以我们不能像可执行文件myapp那样执行它(./myapp)。那如何执行它且它有什么作用呢?请看下面Makefile3:

all: myapp

# Which compiler
CC = gcc

# Where are include file kept
INCLUDE = .

# Options for development
CFLAGS =  - g -Wall -ansi

# Options for release
# CFLAGS = -O -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 myapp

Makefile3在Makefile2的基础上加上了

clean:
        -rm main.o 2.o 3.o myapp

这里的clean就是一个伪目标,为了表示是伪目标,一般如下写:

.PHNOY: clean
clean:
        -rm main.o 2.o 3.o myapp

删除旧安装,利用Makefile3重新make。

  rm *.o myapp

  make -f Makefile3

发现与上面执行Makefil2的命令是一样的。

其实如下命令与  rm *.o myapp是一样的。

  make -f Makefile3 clean

可见,伪目标需要显示调用。

 

内置规则:

  如果makefile文件叫Makefile或makefile或GNUmakefile,而不是如上面那样叫Makefile1、Makefile2之流,则没有必要做精确说明,直接进行如下命令即可:

        make

伪目标clean执行命令如下:

        make claen

 

后缀和模式规则:

  上面文件都是.c文件,假设是.cpp文件呢。

想要增加一条新的后缀规则,首先需要在makefile文件中增加一行语句,告诉make命令这个新的后缀名。然后即可用主穿上新的后缀名来定义规则。make使用特殊语法

.<old_suffix>.<new_suffix>

来定义一条通用规则,利用该规则可以从带有旧后缀名的文件创建带有新的后缀的文件,并保留原文件的前半部分。

下面是makefile文件一个片段,它用一个新的通用规则来将.cpp文件编译为.o文件。

.SUFFIXES: .cpp

.cpp.o:
        $(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<

特殊依赖关系.cpp.o:告诉make,紧随其后的规则是用于将后缀名为.cpp的文件转换为后缀名为.o的文件。

-xc++标志的作用是告诉gcc编译器这是一个c++源文件。

$< 为自动化变量,表示当前依赖文件的名字。还有几个重要的自动化变量:

$@ 表示当前目标的名字。

$? 表示当前目标所依赖的文件列表中比当前目标文件还要新的文件。

$* 表示不包括后缀名的当前依赖文件名字。

通过可用能配符%来实现同样的原理:

%.cpp: %.o
        $(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<

 

用make管理函数库

函数库实际上就是文件,通常是以.a(archive)为后缀名,该文件中包含了一组目标文件。

下面将文件2.o和3.o放入函数库mylib.a中:

all: myapp

# Which compiler
CC = gcc

# Where are include file kept
INCLUDE = .

# Options for development
CFLAGS =  - g -Wall -ansi

# Options for release
# CFLAGS = -O -Wall -ansi

# Local Libraries
MYLIB = mylib.a

myapp: main.o $(MYLIB)
        $(CC) -o myapp $(MYLIB)

$(MYLIB): $(MYLIB)(2.o) $(MYLIB)(3.o)

main.o: main.c a.h

2.o: 2.c a.h b.h

3.o: 3.c b.h c.h

clean:
        -rm main.o 2.o 3.o myapp $(MYLIB)

其中

main.o: main.c a.h

2.o: 2.c a.h b.h

3.o: 3.c b.h c.h

利用默认规则来实现。

下面来测试这个新版的makefile文件Makefile4:

rm -f myapp *.o mylib.a

make -f Makefile4

执行如下:

  gcc -g -Wall -ansi -c -o main.o main.c

  gcc -g -Wall -ansi -c -o 2.o 2.c
  ar rv mylib.a 2.o

  a - 2.o

  gcc -g -Wall -ansi -c -o 3.o 3.c 
  ar rv mylib.a 3.o

  a - 3.o

  gcc -o myapp main.o mylib.a

用于管理函数库的语法是lib(file.o),它的含义是目标文件file.o是存储在函数库lib.a中的。make命令用一个内置规则来管理函数库,该规则的常见形式如下所示:

  .c.a:

  $(CC) -c $(CFLAGS) $<

  $(AR) $(ARFLGAS) $@ $*.o

宏$(AR)和$(ARFLAGS)的默认取值通常分别是命令ar和选项rv,你可利用man ar来查看相关选项的含义。

上面语法告诉make,要想从.c文件得到.a文件,它必须应用两条规则:

1. $(CC) -c $(CFLAGS) $<              必须编译源文件以生成目标文件

2. $(AR) $(ARFLGAS) $@ $*.o        用ar命令将新的目标文件添加到函数库中

 

最后我贴出实习做Fcitx输入法项目时用的一个makefile例子:

CC = gcc
CXX = g++
CFLAGS = -Ifctix -Iui -I. -Wall
CXXFLAGS = -Ifcitx -Iui -I. -Wall
BUILD = build
TARGET = input
FLTK_PATH = ~/workspace/fltk-1.3.0/fltk-config --cxxflags --ldflags

C_SRCS = fcitx/py.c fcitx/pymap.c fcitx/punc.c 
CPP_SRCS = main.cpp ui/MwButton.cpp ui/MwLabel.cpp ui/PopupWidget.cpp fcitx/KeyboardWidget.cpp fcitx/Fcitx.cpp

OBJS = $(BUILD)/main.o $(BUILD)/py.o $(BUILD)/pymap.o $(BUILD)/punc.o $(BUILD)/MwButton.o $(BUILD)/MwLable.o \
       $(BUILD)/PopupWidget.o $(BUILD)/KeyboardWidget.o $(BUILD)/Fcitx.o

TARGET : OBJS
    $(CXX) $(OBJS) -o $(TARGET) -L$(FLTK_PATH)
    rm -rf build

OBJS:
    $(CC) $(CFLAGS) -c $(C_SRCS)
    $(CXX) $(CXXFLAGS) $(FLTKA_PATH) -c $(CPP_SRCS)
    if ! [ -d build ]; then mkdir build; fi
    mv *.o $(BUILD)

all : TARGET DECTS
DICTS_DIR = /home/edan/mnt/input_dict
DICTS:
    if ! [ -d $(DICTS_DIR)]; then mkdir $(DICTS_DIR);
        cp ./dicts/* $(DICTS_DIR)
    if

.PHONY : then
clean:
    rm $(OBJS) $(TARGET)

 

Refference:

(1)《Linux程序设计》 -Neil Matthew     Richard Stones著 第4版第9章开发工具

(2)《跟我一起写 Makefile》-陈皓

(3)makefile官方文档:http://www.gnu.org/software/make/manual/make.html#Introduction

转载请注明:二十画生 » makefile学习笔记

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址