title: 2017-6-15makefile
tags:makefile的書寫和熟悉
示例
# 指令編譯器和選項
CC=gcc
CFLAGS=-lssl -lcrypto -ldl -Wall -g -DDEBUG -std=gnu99
# 宏定義
DEFS = -DTEST_ADD -DTEST_SUB=1
CFLAGS += $(DEFS)
# 頭文件查找路徑
INC = -Iport -I../../modbus/rtu \
-I../../modbus/ascii -I../../modbus/include -I../../modbus/tcp
# 靜態鏈接庫
LDFLAGS =
LDLIBS = -lpthread
# 目標文件
TARGET=test
# 源文件
SRCS = test.c \
./test-add/test-add.c \
./test-sub/test-sub.c
# 頭文件查找路徑
INC = -I./test-add -I./test-sub
# 目標文件
OBJS = $(SRCS:.c=.o)
# 鏈接為可執行文件
$(TARGET):$(OBJS)
# @echo TARGET:$@
# @echo OBJECTS:$^
[tab]$(CC) -o $@ $^
clean:
[tab]rm -rf $(TARGET) $(OBJS)
# 連續動作,請清除再編譯鏈接,最后執行
exec:clean $(TARGET)
[tab]@echo 開始執行
[tab]./$(TARGET)
[tab]@echo 執行結束
# 編譯規則 $@代表目標文件 $< 代表第一個依賴文件
%.o:%.c
[tab]$(CC) $(CFLAGS) $(INC) -o $@ -c $<
# 指定編譯器
CC = gcc
# CFLAG包括頭文件目錄
CFLAGS = -g -Wall
# 頭文件查找路徑
INC = -Iport -I../../modbus/rtu \
-I../../modbus/ascii -I../../modbus/include -I../../modbus/tcp
# 靜態鏈接庫
LDFLAGS =
LDLIBS = -lpthread
# 目標
TARGET = tcpmodbus
# 源文件
SRC = demo.c port/portother.c \
port/portevent.c port/porttcp.c \
../../modbus/mb.c ../../modbus/tcp/mbtcp.c \
../../modbus/functions/mbfunccoils.c \
../../modbus/functions/mbfuncdiag.c \
../../modbus/functions/mbfuncholding.c \
../../modbus/functions/mbfuncinput.c \
../../modbus/functions/mbfuncother.c \
../../modbus/functions/mbfuncdisc.c \
../../modbus/functions/mbutils.c
# 源文件編譯為目標文件
OBJS = $(SRC:.c=.o)
# 鏈接為可執行文件
$(TARGET): $(OBJS)
[tab]$(CC) $^ -o $@ $(LDFLAGS) $(LDLIBS)
# 清除可執行文件和目標文件
clean:
[tab]rm -f $(OBJS)
[tab]rm -f $(TARGET)
# 編譯規則 加入頭文件 $@代表目標文件 $< 代表第一個依賴文件
%.o:%.c
[tab]$(CC) $(CFLAGS) $(INC) -o $@ -c $<
主要目的
是為了把一些可以變化的參數,編譯到文件里面。
所以要關注#define 的作用域。
不管是typedef還是define,其作用域都不會擴展到別的文件,即使是同一個程序的不同文件,也不能互相使用。
語法概要
target... : prerequisites ...
command
【target】 也就是一個目標文件,可以是Object File,也可以是執行文件。還可以是一個標簽(Label)。
【prerequisites】 就是,要生成那個target所需要的文件或是目標。
【command】 也就是make需要執行的命令。(任意的Shell命令)
這個是一個大概的格式,后邊所有的語法和變量都是為了簡化這種寫法。
【:】冒號是表示的依賴關系。依賴關系的實質上就是說明了目標文件是由哪些文件生成的,換言之,目標文件是哪些文件更新的。
make會一層又一層地去找文件的依賴關系,直到最終編譯出第一個目標文件。也就是【target】。
在Makefile中的命令,必須要以[Tab]鍵開始。
Makefile里主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件指示和注釋。
顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。
隱晦規則。由于我們的make有自動推導的功能,所以隱晦的規則可以讓我們比較粗糙地簡略地書寫Makefile,這是由make所支持的。
變量的定義。在Makefile中我們要定義一系列的變量,變量一般都是字符串,這個有點你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
文件指示。其包括了三個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣;另一個是指根據某些情況指定Makefile中的有效部分,就像C語言中的預編譯#if一樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在后續的部分中講述。
注釋。Makefile中只有行注釋,和UNIX的Shell腳本一樣,其注釋是用“#”字符,這個就像C/C++中的“//”一樣。如果你要在你的Makefile中使用“#”字符,可以用反斜框進行轉義,如:“#”。
GNU的make工作時的執行步驟入下:(想來其它的make也是類似)
讀入所有的Makefile。
讀入被include的其它Makefile。
初始化文件中的變量。
推導隱晦規則,并分析所有規則。
為所有的目標文件創建依賴關系鏈。
根據依賴關系,決定哪些目標要重新生成。
執行生成命令。
規則包含兩個部分,一個是依賴關系,一個是生成目標的方法。
make一般是使用環境變量SHELL中所定義的系統Shell來執行命令,默認情況下使用UNIX的標準Shell——/bin/sh來執行命令。
變量
在 Makefile中的定義的變量,就像是C/C++語言中的宏一樣,他代表了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。其與C/C++所不同的是,你可以在Makefile中改變其值。在Makefile中,變量可以使用在“目標”,“依賴目標”,“命令”或是 Makefile的其它部分中。
變量在聲明時需要給予初值,而在使用時,需要給在變量名前加上“$”符號,但最好用小括號“()”或是大括號“{}”把變量給包括起來。如果你要使用真實的“$”字符,那么你需要用“$$”來表示。變量可以使用在許多地方,如規則中的“目標”、“依賴”、“命令”以及新的變量中。
$@--目標文件,$^--所有的依賴文件,$<--第一個依賴文件。
= 是最基本的賦值
:= 是覆蓋之前的值
?= 是如果沒有被賦值過就賦予等號后面的值
+= 是添加等號后面的值
模式規則
%.o : %.c ; <command ......>
其含義是,指出了怎么從所有的[.c]文件生成相應的[.o]文件的規則。如果要生成的目標是"a.o b.o",那么"%c"就是"a.c b.c"
BUG記錄
用makefile定義了宏-DKEY="hello world"
但是gcc這樣宏會忽略雙引號。如果當成左值,則會把這句話(just)當成了聲明變量。
并且
宏中有#運算符時,參數不會被展開
為了能讓參數被順利展開所以就不可以使用"#"符號,最終的宏定義如下
#define HELLO hello world
#define _A(str) _TMP(str)
#define _TMP(str) #str
std::cout << _A(HELLO) << std::endl;
1.展開_A,由于沒有"#"符號,HELLO被順利展開,變為 _TMP(hello world)
2._TMP把參數hello world轉換為字符串,也就是加上雙引號,變為 "hello world"