Makefile 详解


原文链接: Makefile 详解

编译时混合使用动态库和静态库

     编译某个测试代码时,出现了下面的错误:

g++ -std=c++11 -o testlurkcli main.cpp -L. -llurkcli-lasl -static

/usr/bin/ld: cannot find -lstdc++
/usr/bin/ld: cannot find -lm
/usr/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status

     这个错误,是在最后的链接阶段,没有找到 libstdc++,libm,libc等这几个库。正常而言,这几个库的动态库都是存在的,这里因为使用了”-static”选项,导致链接时没有找到这几个库的静态版本。

     网上查了一下,大部分是推荐把这几个库的静态库版本找到并软连接到/usr/lib64/中。

     不过这里采用一种动态库和静态库混合编译的方法去解决。具体编译过程如下:

# g++ -std=c++11 main.cpp liblurkcli.a libasl.a -lpthread-o testlurkcli

     或者:

# g++ -std=c++11 main.cpp -L.  -llurkcli -lasl -lpthread -o testlurkcli

     或者:

# g++ -v -std=c++11 main.cpp -L. -Wl,-Bstatic -llurkcli-lasl -Wl,-Bdynamic -lpthread  -otestlurkcli

      注意,最后一种方式,使用-Wl,-Bstatic以及-Wl,-Bdynamic,给连接器ld传递链接选项,-Wl,-Bstatic使得后面的-l库使用静态连接的方式,而-Wl,-Bdynamic使得后面的-l库使用动态链接的方式。所以,上面的命令中,-llurkcli –lasl使用的是静态连接,而-lpthread,以及连接器自己默认连接的-lstdc++, -lm, -lgcc_s, -lgcc, -lc, -lgcc_s, -lgcc,都是采用的动态链接。

     下面的话,摘自:

https://ftp.gnu.org/pub/old-gnu/Manuals/ld-2.9.1/html_node/ld_3.html#SEC3

--start-group和--end-group 解决 ld 链接 error 时依赖顺序的问题

原因就是链接多个静态库,且这些静态库之间有依赖时,静态库在command line的顺序不对
举例:假设liba.a 依赖于libb.b,目标test需要liba和libb,那么生成test的命令应该为
gcc -o test -la -lb 而不是gcc -o test -lb -la

也可以使用ld的 --start-group xxx --end-group来将所有相互依赖的静态库放到xxx位置,只有就能保证search多次了

arm-himix200-linux-gcc -o rtmp -w -DISP_V2 -DHI_RELEASE -DVER_X=1 -DVER_Y=0 -DVER_Z=0  -DVER_P=0 -DVER_B=10 -DUSER_BIT_32 -DKERNEL_BIT_32 -Wno-date-time -DSENSOR0_TYPE=SONY_IMX335_MIPI_5M_30FPS_12BIT -DSENSOR1_TYPE=SONY_IMX335_MIPI_5M_30FPS_12BIT 
-mcpu=cortex-a7 -mfloat-abi=softfp -mfpu=neon-vfpv4 -fno-aggressive-loop-optimizations -ldl -ffunction-sections -fdata-sections -O2 -fstack-protector-strong -fPIC  $(SRCS) -Wl,--start-group $(INCLUDE) $`(LIBS) -Wl,--end-group

大致说明如下:

使用gcc参数-Wl,–gc-sections,不链接未用函数,减小可执行文件大小

  1. 在编译C、Ada源文件(C++也可以),在gcc/g++编译选项中增加-ffunction-sections、-fdata-sections,在编译生成的.o目标文件中,会将每个函数或数据段,放在各种单独独立的section中;
  2. 在链接生成最终可执行文件时,如果带有-Wl,--gc-sections参数,并且之前编译目标文件时带有 -ffunction-sections -fdata-sections 参数,则链接器ld不会链接未使用的函数,从而减小可执行文件大小;
  3. 如果使用了-r的链接参数,来产生重定位的输出,需要显示的调用-e参数来指定程序入口。否则-Wl,--gc-sections不会生效。

CC = gcc
CXX = g++
LINK = gcc
LD = ld
# prefix ?= arm-none-eabi-
debug  ?= 0

CC  = $(prefix)gcc
AS  = $(prefix)as
LD  = $(prefix)ld
GDB = $(prefix)gdb
OC  = $(prefix)objcopy
# CFLAGS_D = $(COMPILER_FLAGS) -c -g -O0 -Wall -Werror
# CFLAGS_R = $(COMPILER_FLAGS) -c -g -O2  -Werror
# CXXFLAGS_D = $(COMPILER_FLAGS) -c -g -O0  -Werror
# CXXFLAGS_R = $(COMPILER_FLAGS) -c -g -O2  -Werror

INC+= -I. -Isrc
LIBS +=-lm -lpthread
RELEASE = 0

TARGET = epoll
# CCFLAGS = $(CFLAGS_R)

ifeq ($(RELEASE),0)
    # debug
	CFLAGS = $(CFLAGS_D)
	CXXFLAGS = $(CXXFLAGS_D)
else
    # release
	CFLAGS = $(CFLAGS_R)
	CXXFLAGS = $(CXXFLAGS_R)
endif

ifeq ($(DEBUG), 1)
	CFLAGS += -g
	CFLAGS += -DDEBUG
else
	CFLAGS += -O2
	CFLAGS += -DNDEBUG
endif

TARGET_PATH := $(PWD)
# TARGET := $(SRCS:%.c=%)
TARGET :=tlog

SRCS := $(wildcard *.c)
CPPSRC := $(wildcard *.cpp)
OBJS  := $(SRCS:%.c=%.o)
OBJS +=$(ASM:%.s=%.o)
OBJS +=$(CPPSRC:%.cpp=%.o)


LIBFILE = libtlog.a

.PHONY: all
all: $(TARGET) $(LIBFILE)

$(LIBFILE): $(OBJS)
	$(RM) $@ && $(AR) rcs $(OUTPUT)$@ $(OBJS)

$(TARGET): $(OBJS)
	gcc $(LDFLAGS) $(OBJS) -o $@

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

%.o: %.cpp
	$(CXX) $(CCFLAGS) -c $< -o $@

%.o: %.s
	$(CC) $(CFLAGS) -c $< -o $@

.PHONY: clean
clean:
	$(RM) $(LIB_OBJS) $(LIBFILE)
	rm -rf $(TARGET)
	rm -rf $(OBJS)

海思中的Makefile

ifeq ($(PARAM_FILE), )
     PARAM_FILE:=../Makefile.param
     include $(PARAM_FILE)
endif

# target source
OBJS  := $(SMP_SRCS:%.c=%.o)

CFLAGS += $(COMM_INC)

MPI_LIBS += $(REL_LIB)/libhdmi.a

.PHONY : clean all

all: $(TARGET)

$(TARGET):$(COMM_OBJ) $(OBJS)
	@$(CC) $(CFLAGS) -lpthread -lm -o $(TARGET_PATH)/$@ $^ -Wl,--start-group $(MPI_LIBS) $(SENSOR_LIBS) $(AUDIO_LIBA) $(REL_LIB)/libsecurec.a -Wl,--end-group

clean:
	@rm -f $(TARGET_PATH)/$(TARGET)
	@rm -f $(OBJS)
	@rm -f $(COMM_OBJ)

cleanstream:
	@rm -f *.h264
	@rm -f *.h265
	@rm -f *.jpg
	@rm -f *.mjp
	@rm -f *.mp4

鸿蒙代码中的Makefile

CROSS_COMPILE=arm-hisiv300-linux-

CC=$(CROSS_COMPILE)gcc
CXX=$(CROSS_COMPILE)g++
AR=$(CROSS_COMPILE)ar
RANLIB= $(CROSS_COOMPILE)ranlib
STRIP=$(CROSS_COMPILE)strip


TARGET   = hello

CC       = gcc
CFLAGS   = -std=c99 -Wall -I.

LINKER   = gcc
SRC_DIR   = src
OBJ_DIR   = bin/obj
BIN_DIR   = bin

SOURCES  := $(wildcard $(SRC_DIR)/*.c)
OBJECTS  := $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
RM_R      = rm -r
MKDIR_P   = mkdir -p

LIBS = 
LDFLAGS  = -Wall -I. -lm  
LDFLAGS += -ldl -ffunction-sections -fdata-sections -Wl,--gc-sections -O2 -fstack-protector-strong -fPIC 


$(BIN_DIR)/$(TARGET): $(OBJECTS)
	@$(LINKER) $(OBJECTS) $(LDFLAGS) -o $@ -Wl,--start-group $(INCS) $(LIBS) -Wl,--end-group

$(OBJECTS): $(OBJ_DIR)/%.o : $(SRC_DIR)/%.c
	@$(MKDIR_P) ${BIN_DIR} $(OBJ_DIR)
	@$(CC) $(CFLAGS) -c $< -o $@

.PHONY: clean
clean:
	@$(RM_R) $(OBJ_DIR) $(BIN_DIR)




arm-himix200-linux-gcc -o rtsp-h264 -Dhi3516dv300 -DSENSOR0_TYPE=SONY_IMX327_MIPI_2M_30FPS_12BIT -DSENSOR1_TYPE=SONY_IMX327_MIPI_2M_30FPS_12BIT -DHI_RELEASE -DHI_XXXX -DISP_V2 -DHI_ACODEC_TYPE_INNER  -mcpu=cortex-a7 -mno-unaligned-access -fno-aggressive-loop-optimizations -ffunction-sections -fdata-sections  $(INCLUDE) $(LIBS)

变量的基本赋值
知道了如何定义,下面我们来说一下 Makefile 的变量的四种基本赋值方式:
简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。
递归赋值 ( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
追加赋值 ( += ) 原变量用空格隔开的方式追加一个新值。
简单赋值
x:=foo
y:=$(x)b
x:=new
test:

  @echo "y=>$(y)"
  @echo "x=>$(x)"

Linux 变量

ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)

建立一个入口Makefile执行指定子目录中的Makefile


#编译指定子目录
# SUBDIRS=libskt dir2 dir3
#编译所有子目录
SUBDIRS = $(shell ls -d src/* |grep -v "output")
.PHONY: all clean
all:
	for subdir in $(SUBDIRS); do \
		echo "Make in $$subdir";\
		make -C $$subdir;\
	done

.PHONY: clean
clean:
	@echo Making clean
	@list='$(SUBDIRS)'; for subdir in $$list; do \
		echo "Clean in $$subdir";\
		$(MAKE) -C $$subdir clean;\
	done

常用内置变量

查看makefile 变量值 make -p -p, --print-data-base Print make's internal database.即打印makefile 的执行规矩等。从而查看到相应的makefile中的各个变量的值。

$(CURDIR)

Makefile 中变量赋值

1:尽在Makefile文件的目标项冒号后的另起一行的代码才是shell代码。

第一种方式:
xx = xx1 // 这里时makefile代码
第二种方式:
yy:xx = xx2 // 这是是makefile代码,makefile允许变量赋值时,'='号两边留空格 ??????可以吗
第三种shell方式:
yy:
a=xx3 // 只有这里是shell代码 ,shell不允许‘=’号两边有空格哦。
yy:
a= xx3 // 只有这里是shell代码 ,shell不允许‘=’号两边有空格哦。
注意:此时a的值是 " xx3", 注意多了一个空格
有一个例外:
xx=$(shell 这里的代码也是shell代码)

变量传递

2:Makefile中的shell,每一行是一个进程,不同行之间变量值不能传递。所以,Makefile中的shell不管多长也要写在一行。
eg:

SUBDIR=src example

all:
	// 这里往下是一行shell
   @for subdir in $(SUBDIR); \
   do \
       echo "building " $$subdir; \
   done

变量引用 Makefile变量$ shell的变量$$

3:Makefile中的变量以$开头,使用$(VAR)或${VAR}来引用变量的定义。 所以,为了避免和shell的变量冲突,shell的变量以·$$·开头

注意:Makefile中在对一些简单变量的引用,我们也可以不使用“()”和“{}”来标记变量名,而直接使用“$x”的格式来实现,此种用法仅限于变量名为单字符的情况。另外自动化变量也使用这种格式。对于一般多字符变量的引用必须使用括号了标记,否则make将把变量名的首字母作为作为变量而不是整个字符串(“$PATH”在Makefile中实际上是“$(P)ATH”)。这一点和shell中变量的引用方式不同。shell中变量的引用可以是“${xx}”或者“$xx”格式。但在Makefile中多字符变量名的引用只能是“$(xx)”或者“${xx}”格式。

eg1:从当前目录路径中提取出 /application 或 /base_class 之前的部分
PROJECT_ROOT_DIR = $(shell pwd | awk -F'/application|/base_class' '{print $$1}')
eg2:上例中$$subdir就是shell中的变量, 而$(SUBDIR)是Makefile的中的变量

Makefile中规避错误以及$符号的转义 -> $$

在处理时, 需要注意下Makefile对$符号的处理. Makefile的变量大都是$(VAR)的形式, 因此在增加$(DESTDIR)就需要将$字符转义一下(引用Bash变量的时候也一样). 因此我们可以用sed来实现一下:

sed -i -e '/^install/,/^\s*$$/{s/$$(PREFIX)/$$(DESTDIR)\/$$(PREFIX)/g}' Makefile

修改完之后, 打包完成~

基本原

.PYTHON: 伪目标总是不如其它文件“新”,因此它总是被执行。
@ 使命令在被执行前不被回显。
– 使任何命令行的任何非零退出状态都被忽略。 忽略 exit 1

  • 使命令行可以通过指定 -n、-q 或 -t 选项来执行。

模式变量 %

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。
如果我们的定义成“%.o”,意思是我们的集合中都是以“.o”结尾的,而如果我们的定义成“%.c”,意思是对所形成的目标集进行二次定义,其计算方法是,取模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”进行转义,来标明真实的“%”字符。

看一个例子:

objects = foo.o bar.o
all: $(objects)
$(objects):
%.o: %.c
  $(CC) -c $(CFLAGS) $< -o $@

上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,
也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.cbar.c”。
而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:

foo.o : foo.c
  $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
  $(CC) -c $(CFLAGS) bar.c -o bar.o

试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子:

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c

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

$(filter %.elc,$(files)): %.elc: %.el

       emacs -f batch-byte-compile $<

$(filter%.o,$(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。

自动变量 $

$@ -- 代表目标文件(target)
$^ -- 代表所有的依赖文件(components)
$< -- 代表第一个依赖文件(components中最左边的那个)。

要生成目标 ... : 生成目标所需的依赖 ...

command #必须要以[Tab]键开始

export 传递变量到下级 Makefile 中

$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于
目标中模式定义的集合。
$% 仅匹配目标文件
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a
(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix
下是[.a],Windows 下是[.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式( 即"%")定义的,那么"$<"将
是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$? 依赖的集合(只提取比目标新的)
所有比目标新的依赖目标的集合。以空格分隔。
$^ 依赖的集合(去重)
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量
会去除重复的依赖目标,只保留一份。
$+ 依赖的集合(不去重)
这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$* 去除后缀名(不推荐使用)
这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的
模式是"a.%.b",那么,"$"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比
较有较。如果目标中没有模式的定义,那么"$
"也就不能被推导出,但是,如果目标文件的
后缀是 make 所识别的,那么"$"就是除了后缀的那一部分。例如:如果目标是"foo.c",因
为".c"是 make 所能识别的后缀名,所以,"$
"的值就是"foo"。这个特性是 GNU make 的,

很有可能不兼容于其它版本的 make,所以,你应该尽量避免使用"$",除非是在隐含规则
或是静态模式中。如果目标中的后缀是 make 所不能识别的,那么"$
"就是空值。

在Makefile中写shell代码有点诡异,和不同的shell语法不太一样,如果不了解,看Makefile会莫名其妙。下面总结了一些。

Makefile和Shell

  1. Makefile本质上来讲也是shell脚本,即每条command都是shell进程,运行完shell进程都会退出

    test:
    gcc -c main.c -o main.o
    gcc -c a.c -o a.o
    

    这样输入make test,结果相当于两个进程,都退出了。

    等价于在shell下输命令一样。

    my_test:
    cd /home;mkdir test
    

这里make my_test,结果相当于一个进程。当前目录是/root 而

own_test:
  cd /home
  mkdir test

此时make own_test,相当于两个进程,
第一个进程是cd /home,运行完回到了/root。这时再运行第二个shell命令就会在/root下创建一个test目录.(makefile中的shell进程命令,跟直接在shell输的命令相同,但是它都会结束本身,即exit)

变量输出 @隐藏自身的输出 - 出错继续运行

如果make执行的命令前面加了@字符,则不显示命令本身而只显示它的结果; Android中会定义某个变量等于@,例如 hide:= @
通常make执行的命令如果出错(该命令的退出状态非0)就立刻终止,不再执行后续命令,但如果命令前面加了-号,即使这条命令出错,make也会继续执行后续命令。
通常rm命令和mkdir命令前面要加-号,因为rm要删除的文件可能不存在,mkdir要创建的目录可能已存在,这两个命令都有可能出错,但这种错误是应该忽略的。

1、在Makefile中只能在target中调用Shell脚本,其他地方是不能输出的。比如如下代码就是没有任何输出:

VAR="Hello"
echo "$(VAR)"

all:
  .....

以上代码任何时候都不会输出,没有在target内,如果上述代码改为如下:

VAR="Hello"

all:
   echo "$(VAR)"
   .....

以上代码,在make all的时候将会执行echo命令。

最后打印结果是:
echo ""Hello""
"Hello"

2、在Makefile中执行shell命令,一行创建一个进程来执行。这也是为什么很多Makefile中有很多行的末尾都是“; \”,以此来保证代码是一行而不是多行,这样Makefile可以在一个进程中执行,例如:

SUBDIR=src example
all:
   @for subdir in $(SUBDIR); \
   do\
       echo "building "; \
   done

上述可以看出for循环中每行都是以”; \”结尾的。

3、Makefile中所有以$打头的单词都会被解释成Makefile中的变量。如果你需要调用shell中的变量(或者正则表达式中锚定句位$),都需要加两个$符号($$)。实例如下:

PATH="/data/"

all:
   echo ${PATH}/*Makefile中变量,即"/data"*/
   echo $$PATH /*shell中的变量*/

例子中的第一个${PATH}引用的是Makefile中的变量,而不是shell中的PATH环境变量,后者引用的事Shell中的PATH环境变量。

以上三点的是Makefile调用shell应该注意的地方,写Makefile一定要注意。

go arm makefile

#
# Makefile
#

# Path to raspberry pi mounted root (by sshfs)
RPIROOT=/home/k/mnt/pi/

# enable sdjournal
#GOTAGS=
GOTAGS=-tags 'sdjournal'

#
VERSION=1.2
REVISION=`git describe --always`
DATE=`date +%Y%m%d%H%M%S`
USER=`whoami`
BRANCH=`git branch | grep '^\*' | cut -d ' ' -f 2`
LDFLAGS="\
	-X github.com/prometheus/common/version.Version=$(VERSION) \
	-X github.com/prometheus/common/version.Revision='$(REVISION) \
	-X github.com/prometheus/common/version.BuildDate=$(DATE) \
	-X github.com/prometheus/common/version.BuildUser=$(USER) \
	-X github.com/prometheus/common/version.Branch=$(BRANCH)"
LDFLAGS_PI="-w -s \
	-X github.com/prometheus/common/version.Version=$(VERSION) \
	-X github.com/prometheus/common/version.Revision=$(REVISION) \
	-X github.com/prometheus/common/version.BuildDate=$(DATE) \
	-X github.com/prometheus/common/version.BuildUser=$(USER) \
	-X github.com/prometheus/common/version.Branch=$(BRANCH)"

logmonitor: build

.PHONY: build
build: 
	CGO_ENABLED=1 go build $(GOTAGS) -v -o logmonitor -ldflags $(LDFLAGS)

.PHONY: build_pi
build_pi: 
	GOGCCFLAGS="-fPIC -O4 -Ofast -pipe -march=native -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -s" \
		GOARCH=arm GOARM=6 CGO_ENABLED=1 GOOS=linux \
		CC=arm-linux-gnueabi-gcc  \
		CXX=arm-linux-gnueabi-g++ \
		CGO_LDFLAGS="-L$(RPIROOT)/lib/arm-linux-gnueabihf -lsystemd \
			-Wl,-rpath-link=$(RPIROOT)/lib/arm-linux-gnueabihf \
			-Wl,-rpath-link=$(RPIROOT)/lib/ \
			-Wl,-rpath-link=$(RPIROOT)/usr/lib/ \
			-Wl,-rpath-link=$(RPIROOT)/usr/lib/arm-linux-gnueabihf " \
		go build  $(GOTAGS) -v -o logmonitor-arm -ldflags $(LDFLAGS_PI)

logmonitor-arm: build_pi

.PHONY: install_pi
install_pi: logmonitor-arm
	ssh pi "systemctl --user stop logmonitor"
	ssh pi "[ -f ~/prometheus/logmonitor-arm ] && mv -f ~/prometheus/logmonitor-arm ~/prometheus/logmonitor-arm.old"
	scp logmonitor-arm pi:prometheus/
	ssh pi "systemctl --user start logmonitor"
	ssh pi "systemctl --user status logmonitor"

.PHONY: build_arm5
build_arm5: 
	GOGCCFLAGS="-fPIC -O4 -Ofast -pipe -march=native -marm -s" \
		GOARCH=arm GOARM=5 CGO_ENABLED=1 GOOS=linux \
		CC=arm-linux-gnueabi-gcc  \
		CXX=arm-linux-gnueabi-g++ \
		CGO_LDFLAGS="-L$(RPIROOT)/lib/arm-linux-gnueabihf -lsystemd \
			-Wl,-rpath-link=$(RPIROOT)/lib/arm-linux-gnueabihf \
			-Wl,-rpath-link=$(RPIROOT)/lib/ \
			-Wl,-rpath-link=$(RPIROOT)/usr/lib/ \
			-Wl,-rpath-link=$(RPIROOT)/usr/lib/arm-linux-gnueabihf " \
		go build  $(GOTAGS) -v -o logmonitor-arm5 -ldflags $(LDFLAGS_PI)
	
.PHONY: run
run:
	#go run -v *.go -log.level debug
	CGO_ENABLED="1" go-reload `ls *.go | grep -v _test.go` -log.level debug

.PHONY: clean
clean:
	rm -f logmonitor logmonitor-arm

# vim:ft=make

Makefile 入口函数的实现

很多时候,我们的代码结构需要将源代码划分成多个子模块,每个模块应该可以单独编译和执行,而我们又希望有一个总的Makefile将他们串联起来,本文就是针对这一问题而来的。

方法1:
SUBDIRS = SUB_DIR_NAME_1 SUB_DIR_NAME_2

.PHONY: all
all:
	@list='$(SUBDIRS)'; for subdir in $$list; do \
		echo "Clean in $$subdir";\
		cd $$subdir && $(MAKE);\
	done

.PHONY: clean
clean:
	@list='$(SUBDIRS)'; for subdir in $$list; do \
		echo "Clean in $$subdir";\
		cd $$subdir && $(MAKE) clean;\
	done


方法2:
SUBDIRS = SUB_DIR_NAME_1 SUB_DIR_NAME_2

.PHONY: all
all:
	@list='$(SUBDIRS)'; for subdir in $$list; do \
		echo "Clean in $$subdir";\
		$(MAKE)-C $$subdir;\
	done

.PHONY: clean
clean:
	@echo Making clean
	@list='$(SUBDIRS)'; for subdir in $$list; do \
		echo "Clean in $$subdir";\
		$(MAKE)-C $$subdir clean;\
	done


方法3:
SUBDIRS =  SUB_DIR_NAME_1  SUB_DIR_NAME_2

.PHONY: all $(SUBDIRS)
all: $(SUBDIRS)
$(SUBDIRS):
	$(MAKE) -C $@
SUB_DIR_NAME_2 : SUB_DIR_NAME_1

	
.PHONY: clean
clean:
	@echo Making clean
	@list='$(SUBDIRS)'; for subdir in $$list; do \
		echo "Clean in $$subdir";\
		$(MAKE) -C $$subdir clean;\
	done


注释:
1.
-C DIR
--directory=DIR
在读取Makefile之前,进入目录“DIR”,就是切换工作目录到“DIR”之后执行make。存在多个“-C”选项时,make的最终工作目录是第一个目录的相对路径。如:“make –C / -C etc”等价于“make –C /etc”。一般此选项被用在递归地make调用中。

2.
在递归调用时,使用$(MAKE)而不是make:
1)
变量“MAKE”的值是“make”。如果其值为“/bin/make”那么上边规则的命令就为“cd subdir && /bin/make”。这样做的好处是:当我们使用一个其它版本的make程序时,可以保证最上层使用的make程序和其子目录下执行的make程序保持一致。
2)
变量“MAKE”的这个特点是:在规则的命令行中如果使用变量“MAKE”,标志“-t”、“-n”和“-q”在这个命令的执行中不起作用。尽管这些选项是告诉make不执行规则的命令行,但包含变量“MAKE”的命令行除外,它们会被正常执行。同时,执行make的命令行选项参数被通过一个变量“MAKEFLAGS”传递给子目录下的make程序。
`