如何使用makefile编译不同平台的目标文件(makefile的参数传递)

最近在研究一个嵌入式开发项目,在编写实际的项目代码时,需要临时写一些测试代码对部分功能进行预测试。编写的这些代码,有时候需要在PC机(x86)的平台上运行,有时候则需要在A嵌入式平台(arm端)i.mx6的平台上运行,而还有时候则需要在B嵌入式平台(arm端)mini2440的平台上运行,需要能随时进行切换,编译出对应平台所需要的可执行文件。

为了解决这个问题,最土的办法自然就是写三份makefile,需要编译某一个平台时,拷贝对应的makefile,然后再make。可是这样效率很低,而且很low,自然不予考虑。稍微好一点的办法,则是在makefile中,定义三套编译规则,通过输入make x86,以及make imx6,以及make mini2440这样的目标选择命令,来指示编译器选择某一套编译规则并运行。但是,这样的方法,会导致makefile的文件很冗余,存在多套几乎一模一样的编译规则。如下所示:

# 强制用bash,有的系统/bin/sh默认是用dash的,用dash会导致echo -e显示时多出一个-e
SHELL = /bin/bash

# syspath & tools
COMPILE_x86		 = gcc
COMPILE_arm	 	 = arm-poky-linux-gnueabi-gcc

# build target
TARGET_x86		:= test_x86
TARGET_arm		:= test_arm

# compile option
LIBS			+= -lpthread
CFLAGS			+= -rdynamic -Wall -O2 -Wno-uninitialized
LDFLAGS			+= $(LIBS)

# sources path       
SOURCES			+= ${wildcard *.c}

# build dir
BUILDDIR_x86	?= _build_x86
BUILDDIR_arm	?= _build_arm

TEMPDIR_x86		?= $(BUILDDIR_x86)/temp
TEMPDIR_arm 	?= $(BUILDDIR_arm)/temp

# object files
OBJECTS_x86		:= $(patsubst %.c,$(TEMPDIR_x86)/%.o,$(filter %.c, $(SOURCES)))
DEPENDS_x86		:= $(patsubst %.c,$(TEMPDIR_x86)/%.d,$(filter %.c, $(SOURCES)))

OBJECTS_arm		:= $(patsubst %.c,$(TEMPDIR_arm)/%.o,$(filter %.c, $(SOURCES)))
DEPENDS_arm		:= $(patsubst %.c,$(TEMPDIR_arm)/%.d,$(filter %.c, $(SOURCES)))

# build command
.PHONY: x86 arm clean

x86: mngdir_x86 $(TARGET_x86)

mngdir_x86:
	@mkdir -pv $(TEMPDIR_x86)
	@mkdir -pv $(BUILDDIR_x86)
	
$(TEMPDIR_x86)/%.o: %.c
	@mkdir -pv $(@D)
	$(COMPILE_x86) $(CFLAGS) -c -o $@ $<

$(TEMPDIR_x86)/%.d: %.c
	@echo "$@: $<:$(notdir $*)"
	@mkdir -pv $(@D)
	@set -e; rm -f $@; \
	$(COMPILE_x86) -MM $(CFLAGS) $< > $@.$$$$; \
	sed 's,$(notdir $*)\.o[ :]*,$(TEMPDIR_x86)/$*\.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

$(TARGET_x86): $(OBJECTS_x86) $(DEPENDS_x86)
	$(COMPILE_x86) $(CFLAGS) -o $(BUILDDIR_x86)/$@ $(OBJECTS_x86) $(LDFLAGS)
	@echo "*********************	Bulid $(TARGET_x86)  complete	************************"
	
arm: mngdir_arm $(TARGET_arm)

mngdir_arm:
	@mkdir -pv $(TEMPDIR_arm)
	@mkdir -pv $(BUILDDIR_arm)
	
$(TEMPDIR_arm)/%.o: %.c
	@mkdir -pv $(@D)
	$(COMPILE_arm) $(CFLAGS) -c -o $@ $<

$(TEMPDIR_arm)/%.d: %.c
	@echo "$@: $<:$(notdir $*)"
	@mkdir -pv $(@D)
	@set -e; rm -f $@; \
	$(COMPILE_arm) -MM $(CFLAGS) $< > $@.$$$$; \
	sed 's,$(notdir $*)\.o[ :]*,$(TEMPDIR)/$*\.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

$(TARGET_arm): $(OBJECTS_arm) $(DEPENDS)
	$(COMPILE_arm) $(CFLAGS) -o $(BUILDDIR_arm)/$@ $(OBJECTS_arm) $(LDFLAGS)
	@echo "*********************	Bulid $(TARGET_arm) complete	************************"
	
clean:
	-@rm -fv $(TARGET_x86)
	-@rm -fv $(OBJECTS_x86)
	-@rm -fv $(DEPENDS_x86)
	-@rm -rfv $(TEMPDIR_x86)
	-@rm -rfv $(BUILDDIR_x86)
	-@rm -fv $(TARGET_arm)
	-@rm -fv $(OBJECTS_arm)
	-@rm -fv $(DEPENDS_arm)
	-@rm -rfv $(TEMPDIR_arm)
	-@rm -rfv $(BUILDDIR_arm)
	@echo "*********************	clean all targets complete	************************"

为了简单起见,上述makefile中,只列举了其中两套编译规则,第三套并没有写上。尽管如此,仍然可以看出整个文件很冗长,虽然整个make的过程很简单,但是makefile的可读性却不怎么高,代码看上去不舒服。而且,最大的问题是,可扩展性很差,当我要再增加第三个平台甚至第四个平台时,代码量是呈倍数增长的,显然不适合。

那么,有没有更好的办法呢?答案显然是有的。

通过一番搜索,我找到了以下的思路:

在makefile中可以预先使用一个未定义的变量, 这个变量可以在make执行时传递给它。换句话说,就是可以向make命令传递参数,告诉它怎么去解释makefile这个文件。

网上关于这个思路最多的例子,就是如何使用同一份代码、同一份makefile,却通过给make命令传入不同的参数来编译出debug版本和release版本,直接去百度一抓一大把【可参见其他博主的博文:如何给Make命令来传递参数,如何给Makefile 传入参数,make传递给Makefile参数,等等】,这里就不再赘述。

于是,根据这个思路,我对上述的makefile文件进行了精简和修改,最终的效果如下所示:

# 强制用bash,有的系统/bin/sh默认是用dash的,用dash会导致echo -e显示时多出一个-e
SHELL = /bin/bash

# avalible targets
target_x86		 = x86
target_imx6		 = imx6
target_2440		 = 2440

# build target
TARGET_ARCH		 = $(target)
TARGET_OBJT		:= temptest_$(TARGET_ARCH)

# syspath & tools
ifeq ($(TARGET_ARCH), $(target_x86))
	COMPILE		 = gcc
else ifeq ($(TARGET_ARCH), $(target_imx6))
	COMPILE 	 = arm-poky-linux-gnueabi-gcc
else ifeq ($(TARGET_ARCH), $(target_2440))
	COMPILE		 = arm-linux-gcc
endif

# compile option
LIBS			+= -lpthread
CFLAGS			+= -rdynamic -Wall -O2 -Wno-uninitialized
LDFLAGS			+= $(LIBS)

# sources path
SOURCES			+= ${wildcard *.c}

# build dir
BUILDDIR		?= _build
OBJCTDIR		?= $(BUILDDIR)/object_$(TARGET_ARCH)

# object files
OBJECTS			:= $(patsubst %.c,$(OBJCTDIR)/%.o,$(filter %.c, $(SOURCES)))
DEPENDS			:= $(patsubst %.c,$(OBJCTDIR)/%.d,$(filter %.c, $(SOURCES)))

# build command
.PHONY: all useage help clean

all: mngdir $(TARGET_OBJT)

mngdir:
	@mkdir -pv $(OBJCTDIR)
	@mkdir -pv $(BUILDDIR)

$(OBJCTDIR)/%.o: %.c
	@mkdir -pv $(@D)
	$(COMPILE) $(CFLAGS) -c -o $@ $<

$(OBJCTDIR)/%.d: %.c
	@echo "$@: $<:$(notdir $*)"
	@mkdir -pv $(@D)
	@set -e; rm -f $@; \
	$(COMPILE) -MM $(CFLAGS) $< > $@.$$$$; \
	sed 's,$(notdir $*)\.o[ :]*,$(TEMPDIR_x86)/$*\.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

$(TARGET_OBJT): $(OBJECTS) $(DEPENDS)
	$(COMPILE) $(CFLAGS) -o $(BUILDDIR)/$@ $(OBJECTS) $(LDFLAGS)
	@echo "*********************	Bulid $(TARGET_OBJT) complete	************************"

useage help:
	@echo "Build Help Info"
	@echo "    make target=$(target_x86)     	-- build $(target_x86) arch target"
	@echo "    make target=$(target_imx6)     	-- build $(target_imx6) arch target"
	@echo "    make target=$(target_2440)     	-- build $(target_2440) arch target"

clean:
	-@rm -rfv $(BUILDDIR)
	@echo "*********************	clean all targets complete	************************"

可以看出,修改后的makefile,代码明显精简了,可读性也提高了,最关键的是,扩展性大大提升,日后如果我再需要更换或者增加目标平台时,只需要在文件头处加以修改就搞定!

使用起来的话,只需要输入“make target=x86”命令,或者“make target=imx6”命令,或者“make target=2440”命令,系统就会自动编译出对应平台下的可执行目标文件,而无需对makefile文件本身进行其他多余的操作,大大提升了调试和修改的效率。


2019.01.31,补充以下内容

经过实际项目的研究,最终写出了以下makefile,可完美搞定本问题:

# 强制用bash,有的系统/bin/sh默认是用dash的,用dash会导致echo -e显示时多出一个-e
SHELL = /bin/bash

# 这里先根据用户输入的编译目标选择好相应的编译器,加入-include $(DEPENDS)的目的是为了能够自动识别出头文件的修改并进行跟随编译
ifeq ($(MAKECMDGOALS),pc)
	COMPILE	= gcc
	-include $(DEPENDS)
else ifeq ($(MAKECMDGOALS),imx6)
	COMPILE = /opt/poky/1.7/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc -march=armv7-a -mthumb-interwork -mfloat-abi=hard -mfpu=neon -mtune=cortex-a7
	-include $(DEPENDS)
else ifeq ($(MAKECMDGOALS),2440)
	COMPILE	= arm-linux-gcc
	-include $(DEPENDS)
endif

# 这里定义出所编译的目标架构及最终的可执行文件名
TARGET_ARCH		 = $(MAKECMDGOALS)
TARGET_OBJT		:= test_$(MAKECMDGOALS)

# 这里定义出编译的选项及配置
CFLAGS			+= $(INCLUDES) -rdynamic -Wall -O2 -Wno-uninitialized
LDFLAGS			+= -lpthread

# 这里指定编译过程需要包含的头文件路径,以及要编译的源文件(下面的inc目录和src目录分别存放头文件和源文件,可自定义修改和添加)
INCLUDES		+= -Iinc/h
SOURCES			+= ${wildcard *.c}
SOURCES			+= ${wildcard src/*.c}

# 这里设置好编译过程中的临时目录名称
BUILDDIR		?= _build
OBJCTDIR		?= $(BUILDDIR)/$(TARGET_ARCH)

# 这里定义好编译的规则:即根据.c文件编译出同名的.d和.o文件
OBJECTS			:= $(patsubst %.c, $(OBJCTDIR)/%.o, $(filter %.c, $(SOURCES)))
DEPENDS			:= $(patsubst %.c, $(OBJCTDIR)/%.d, $(filter %.c, $(SOURCES)))

# 这里定义出合法的编译对象
.PHONY: pc imx6 2440 useage help clean

pc imx6 2440: mngdir $(TARGET_OBJT)

mngdir:
	@echo "-------------------------------------------------- Begin to bulid $(TARGET_OBJT)"
	@mkdir -pv $(OBJCTDIR)
	@mkdir -pv $(BUILDDIR)

$(OBJCTDIR)/%.o: %.c
	@mkdir -pv $(@D)
	$(COMPILE) $(CFLAGS) -c -o $@ $<

$(OBJCTDIR)/%.d: %.c
	@echo "$@: $<:$(notdir $*)"
	@mkdir -pv $(@D)
	@set -e; rm -f $@; \
	$(COMPILE) -MM $(CFLAGS) $< > $@.$$$$; \
	sed 's,$(notdir $*)\.o[ :]*,$(OBJCTDIR)/$*\.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

$(TARGET_OBJT): $(OBJECTS) $(DEPENDS)
	$(COMPILE) $(CFLAGS) -o $(BUILDDIR)/$@ $(OBJECTS) $(LDFLAGS)
	@echo "-------------------------------------------------- Bulid $(TARGET_OBJT) complete!"

useage help:
	@echo "How to build"
	@echo "    make pc		-- build target for pc"
	@echo "    make imx6		-- build target for imx6"
	@echo "    make 2440		-- build target for mini2440"

clean:
	@echo "-------------------------------------------------- Begin to clean bulid files"
	-@rm -rfv $(BUILDDIR)
	@echo "-------------------------------------------------- clean all files complete!"

编译时,只需输入“make pc”或者“make imx6”或者“make 2440”即可自动编译出不同平台下的目标文件,输入“make clean”便可一次性清理掉全部的临时文件,输入“make help”或者“make usage”便可查看本makefile的使用帮助,整个过程轻松愉快!

你可能感兴趣的:(玩转linux)