Makefile 优化编译速度

Makefile 优化编译速度

编写一个通用的 Makefile 模板,用来实现任意工程的编译管理架构搭建。

这里我主要有两种想法:
1. 用一个 Makefile 管理所有的文件。所有的文件都放到最顶层的 Makefile 中,一次性直接加载所有的 .c 文件完成编译。
2. 不同目录用不同的 Makefile 进行管理,通过 make -C 来实现链式调用子 Makefile ,子 Makefile 只编译自己的 .c 文件,并再调用 make -C 来编译其下的子 Makefile。最终在顶层 Makefile 中完成对所有 .o 文件的链接。

这里我最开始认为,第一种方式写出来的 Makefile 运行速度应该是会更快的,毕竟,一次性就编译完所有的 .c 文件,不会有其它的额外操作执行。最终测试下来,第二种方式速度更快。下面将对比分析几种不同的 Makefile 方式接介绍为什么第二种速度更快。

1. 工程结构

这里是使用一个小工程做为测试基准。工程结构如下:

.
├── app.c
├── app_conf.c
├── app_conf.h
├── app.h
├── app_ui.c
├── build
│   ├── compile_commands.json
│   └── out
│       ├── app_conf.d
│       ├── app_conf.o
│       ├── app.d
│       ├── app.o
│       ├── app_ui.d
│       ├── app_ui.o
│       ├── component
│       │   ├── app_log
│       │   ├── app_signal
│       │   ├── btmesh_conf
│       │   ├── btmesh_db
│       │   ├── btmesh_prov
│       │   ├── ncp_evt_filter
│       │   ├── ncp_host_bt
│       │   ├── ncp_host_btmesh
│       │   ├── simple_timer
│       │   └── slist
│       ├── main.d
│       ├── main.o
│       ├── object.map
│       └── objs.cache
├── compile_commands.json
├── component
│   ├── app_assert
│   │   └── include
│   │       ├── app_assert_config.h
│   │       ├── app_assert.h
│   │       └── sl_app_assert.h
│   ├── app_log
│   │   ├── app_log.c
│   │   ├── app_log_cli.c
│   │   ├── app_log_config.h_old
│   │   ├── include
│   │   │   ├── app_log_cli.h
│   │   │   ├── app_log_config.h
│   │   │   ├── app_log.h
│   │   │   ├── arm_common_tables.h
│   │   │   ├── arm_const_structs.h
│   │   │   ├── arm_math.h
│   │   │   ├── cmsis_armcc.h
│   │   │   ├── cmsis_armclang.h
│   │   │   ├── cmsis_compiler.h
│   │   │   ├── cmsis_gcc.h
│   │   │   ├── cmsis_iccarm.h
│   │   │   ├── cmsis_version.h
│   │   │   ├── core_armv8mbl.h
│   │   │   ├── core_armv8mml.h
│   │   │   ├── core_cm0.h
│   │   │   ├── core_cm0plus.h
│   │   │   ├── core_cm23.h
│   │   │   ├── core_cm33.h
│   │   │   ├── core_cm3.h
│   │   │   ├── core_cm4.h
│   │   │   ├── core_cm7.h
│   │   │   ├── core_sc000.h
│   │   │   ├── core_sc300.h
│   │   │   ├── efr32mg21a010f1024im32.h
│   │   │   ├── efr32mg21a010f512im32.h
│   │   │   ├── efr32mg21a010f768im32.h
│   │   │   ├── efr32mg21a020f1024im32.h
│   │   │   ├── efr32mg21a020f512im32.h
│   │   │   ├── efr32mg21a020f768im32.h
│   │   │   ├── efr32mg21_acmp.h
│   │   │   ├── efr32mg21_aes.h
│   │   │   ├── efr32mg21_agc.h
│   │   │   ├── efr32mg21_amuxcp.h
│   │   │   ├── efr32mg21b010f1024im32.h
│   │   │   ├── efr32mg21b010f512im32.h
│   │   │   ├── efr32mg21b010f768im32.h
│   │   │   ├── efr32mg21b020f1024im32.h
│   │   │   ├── efr32mg21b020f512im32.h
│   │   │   ├── efr32mg21b020f768im32.h
│   │   │   ├── efr32mg21_bufc.h
│   │   │   ├── efr32mg21_buram.h
│   │   │   ├── efr32mg21_burtc.h
│   │   │   ├── efr32mg21_cmu.h
│   │   │   ├── efr32mg21_devinfo.h
│   │   │   ├── efr32mg21_dma_descriptor.h
│   │   │   ├── efr32mg21_dpll.h
│   │   │   ├── efr32mg21_emu.h
│   │   │   ├── efr32mg21_frc.h
│   │   │   ├── efr32mg21_fsrco.h
│   │   │   ├── efr32mg21_gpcrc.h
│   │   │   ├── efr32mg21_gpio.h
│   │   │   ├── efr32mg21_gpio_port.h
│   │   │   ├── efr32mg21_hfrco.h
│   │   │   ├── efr32mg21_hfxo.h
│   │   │   ├── efr32mg21_i2c.h
│   │   │   ├── efr32mg21_iadc.h
│   │   │   ├── efr32mg21_icache.h
│   │   │   ├── efr32mg21_ldma.h
│   │   │   ├── efr32mg21_ldmaxbar_defines.h
│   │   │   ├── efr32mg21_ldmaxbar.h
│   │   │   ├── efr32mg21_letimer.h
│   │   │   ├── efr32mg21_lfrco.h
│   │   │   ├── efr32mg21_lfxo.h
│   │   │   ├── efr32mg21_lvgd.h
│   │   │   ├── efr32mg21_modem.h
│   │   │   ├── efr32mg21_msc.h
│   │   │   ├── efr32mg21_protimer.h
│   │   │   ├── efr32mg21_prs.h
│   │   │   ├── efr32mg21_prs_signals.h
│   │   │   ├── efr32mg21_rac.h
│   │   │   ├── efr32mg21_rfcrc.h
│   │   │   ├── efr32mg21_rtcc.h
│   │   │   ├── efr32mg21_semailbox.h
│   │   │   ├── efr32mg21_smu.h
│   │   │   ├── efr32mg21_synth.h
│   │   │   ├── efr32mg21_syscfg.h
│   │   │   ├── efr32mg21_timer.h
│   │   │   ├── efr32mg21_ulfrco.h
│   │   │   ├── efr32mg21_usart.h
│   │   │   ├── efr32mg21_wdog.h
│   │   │   ├── em_assert.h
│   │   │   ├── em_common.h
│   │   │   ├── em_device.h
│   │   │   ├── mpu_armv7.h
│   │   │   ├── mpu_armv8.h
│   │   │   ├── rm21z000f1024im32.h
│   │   │   ├── sl_app_log.h
│   │   │   ├── sl_iostream.h
│   │   │   ├── sl_iostream_handles.h
│   │   │   ├── sl_status.h
│   │   │   ├── system_efr32mg21.h
│   │   │   └── tz_context.h
│   │   ├── lib
│   │   ├── Makefile
│   │   └── sl_iostream_handles.c
│   ├── app_signal
│   │   ├── app_signal_posix.c
│   │   ├── app_signal_win.c
│   │   ├── include
│   │   │   └── app_signal.h
│   │   ├── lib
│   │   └── Makefile
│   ├── btmesh_conf
│   │   ├── btmesh_conf.c
│   │   ├── btmesh_conf_distributor.c
│   │   ├── btmesh_conf_executor.c
│   │   ├── btmesh_conf_job.c
│   │   ├── btmesh_conf_task.c
│   │   ├── include
│   │   │   ├── btmesh_conf_config.h
│   │   │   ├── btmesh_conf_distributor.h
│   │   │   ├── btmesh_conf_executor.h
│   │   │   ├── btmesh_conf.h
│   │   │   ├── btmesh_conf_job.h
│   │   │   ├── btmesh_conf_task.h
│   │   │   └── btmesh_conf_types.h
│   │   ├── lib
│   │   └── Makefile
│   ├── btmesh_db
│   │   ├── btmesh_db.c
│   │   ├── include
│   │   │   └── btmesh_db.h
│   │   ├── lib
│   │   └── Makefile
│   ├── btmesh_prov
│   │   ├── btmesh_prov.c
│   │   ├── include
│   │   │   └── btmesh_prov.h
│   │   ├── lib
│   │   └── Makefile
│   ├── ncp_evt_filter
│   │   ├── include
│   │   │   ├── sl_ncp_evt_filter_common.h
│   │   │   ├── sl_ncp_evt_filter_config.h
│   │   │   └── sl_ncp_evt_filter.h
│   │   ├── lib
│   │   ├── Makefile
│   │   └── sl_ncp_evt_filter.c
│   ├── ncp_host_bt
│   │   ├── app_sleep.c
│   │   ├── backup
│   │   │   ├── Makefile
│   │   │   ├── sl_bluetooth.c
│   │   │   ├── sl_bluetooth.c.jinja
│   │   │   ├── sl_bluetooth.h.jinja
│   │   │   ├── sl_bt_dynamic_gattdb_config.c
│   │   │   ├── sl_bt_mbedtls_context.c
│   │   │   ├── sl_btmesh.c
│   │   │   ├── sl_btmesh.c.jinja
│   │   │   ├── sl_btmesh_lib.c
│   │   │   ├── sl_btmesh_ncp_host_api.c
│   │   │   ├── sl_btmesh_ncp_host.c
│   │   │   ├── sl_btmesh_sensor.c
│   │   │   ├── sl_btmesh_serdeser.c
│   │   │   ├── sl_bt_power_control_init.c
│   │   │   ├── sl_bt_rtos_adaptation.c
│   │   │   ├── sl_ncp_sec.c
│   │   │   ├── sl_ncp_sec_legacy.c
│   │   │   └── system.c
│   │   ├── host_comm.c
│   │   ├── include
│   │   │   ├── app_sleep.h
│   │   │   ├── em_core.h
│   │   │   ├── host_comm_config.h
│   │   │   ├── host_comm.h
│   │   │   ├── named_socket.h
│   │   │   ├── ncp_host_config.h
│   │   │   ├── ncp_host.h
│   │   │   ├── sl_atomic.h
│   │   │   ├── sl_bgapi.h
│   │   │   ├── sl_bit.h
│   │   │   ├── sl_bluetooth.h
│   │   │   ├── sl_bt_api_compatibility.h
│   │   │   ├── sl_bt_api.h
│   │   │   ├── sl_bt_ll_config.h
│   │   │   ├── sl_bt_mbedtls_context.h
│   │   │   ├── sl_btmesh_api.h
│   │   │   ├── sl_btmesh_bgapi.h
│   │   │   ├── sl_btmesh_capi_types.h
│   │   │   ├── sl_btmesh_compatibility_macros.h
│   │   │   ├── sl_btmesh_device_properties.h
│   │   │   ├── sl_btmesh_generic_model_capi_types.h
│   │   │   ├── sl_btmesh.h
│   │   │   ├── sl_btmesh.h.jinja
│   │   │   ├── sl_btmesh_lib.h
│   │   │   ├── sl_btmesh_lighting_model_capi_types.h
│   │   │   ├── sl_btmesh_memory_config.h
│   │   │   ├── sl_btmesh_model_specification_defs.h
│   │   │   ├── sl_btmesh_ncp_host.h
│   │   │   ├── sl_btmesh_sensor.h
│   │   │   ├── sl_btmesh_sensor_model_capi_types.h
│   │   │   ├── sl_btmesh_serdeser.h
│   │   │   ├── sl_btmesh_stack_init.h
│   │   │   ├── sl_bt_ncp_host.h
│   │   │   ├── sl_bt_rtos_adaptation.h
│   │   │   ├── sl_bt_stack_config.h
│   │   │   ├── sl_bt_stack_init.h
│   │   │   ├── sl_bt_types.h
│   │   │   ├── sl_bt_version.h
│   │   │   ├── sl_cmsis_os2_common.h
│   │   │   ├── sl_endianness.h
│   │   │   ├── sl_enum.h
│   │   │   ├── sl_gsdk_version.h
│   │   │   ├── sli_bt_api.h
│   │   │   ├── sli_bt_gattdb_def.h
│   │   │   ├── sli_btmesh_api.h
│   │   │   ├── sli_cmsis_os2_ext_task_register.h
│   │   │   ├── sl_malloc.h
│   │   │   ├── sl_ncp_sec.h
│   │   │   ├── sl_slist.h
│   │   │   ├── sl_status.h
│   │   │   ├── sl_stdio.h
│   │   │   ├── sl_string.h
│   │   │   ├── system.h
│   │   │   ├── tcp.h
│   │   │   └── uart.h
│   │   ├── lib
│   │   ├── Makefile
│   │   ├── named_socket.c
│   │   ├── ncp_host.c
│   │   ├── sl_btmesh.c
│   │   ├── sl_btmesh_lib.c
│   │   ├── sl_btmesh_serdeser.c
│   │   ├── sl_bt_ncp_host_api.c
│   │   ├── sl_bt_ncp_host.c
│   │   ├── sl_malloc.c
│   │   ├── system.c
│   │   ├── tcp_posix.c
│   │   ├── tcp_win.c
│   │   ├── uart_posix.c
│   │   └── uart_win.c
│   ├── ncp_host_btmesh
│   │   ├── lib
│   │   ├── Makefile
│   │   ├── sl_btmesh_ncp_host_api.c
│   │   └── sl_btmesh_ncp_host.c
│   ├── simple_timer
│   │   ├── include
│   │   │   ├── sl_simple_timer.h
│   │   │   ├── sl_simple_timer_mac.h
│   │   │   ├── sl_simple_timer_posix.h
│   │   │   └── sl_simple_timer_win.h
│   │   ├── lib
│   │   ├── Makefile
│   │   ├── sl_simple_timer_mac.c
│   │   ├── sl_simple_timer_posix.c
│   │   └── sl_simple_timer_win.c
│   └── slist
│       ├── include
│       │   ├── em_assert.h
│       │   ├── sl_atomic.h
│       │   ├── sl_bit.h
│       │   ├── sl_cmsis_os2_common.h
│       │   ├── sl_endianness.h
│       │   ├── sl_enum.h
│       │   ├── sl_gsdk_version.h
│       │   ├── sli_cmsis_os2_ext_task_register.h
│       │   ├── sl_slist.h
│       │   ├── sl_status.h
│       │   ├── sl_stdio.h
│       │   └── sl_string.h
│       ├── lib
│       ├── Makefile
│       └── sl_slist.c
├── main
├── main.c
├── Makefile
├── README.md
└── tree.ext

46 directories, 264 files

工程中,.c 文件在顶层目录下,以及 component 目录下的子文件夹中。 build 目录是由 Makefile 生成的临时文件夹,用于存放编译生成的中间文件。

2. Makefile 方式 A

针对方式2,这里细分成为两种方式,方式A和方式B。两种方式都是通过顶层 Makefile 开始执行,然后调用子模块的 Makefile。但是子模块再生成目标时,方式A 是通过将其自身编译为 .a 文件,在顶层 Makefile 中去链接所有的 .a 文件来集成子 Makefile 的编译输出;方式B 是只将所有的 .c 编译为 .o 文件,然后由顶层 Makefile 来集成所有的 .o 文件。

由于基本可以确定生成 .a 的名称不会一样,把所有 .a 放到指定路径,在顶层Makefile中,再读取所有的 .a 进行链接。由于这里所有子 Makefile 的目录结构都一致,而且只有一层子 Makefile ,所以这里没有将 .a 放到 build 目录下的同一个文件夹中,但是不影响测试。

顶层 Makefile 如下:

#------------------------------
# 生成程序名称
#------------------------------
target := btmesh_provisioner

#------------------------------
# 选择编译工具链
#------------------------------
CROSS=
#arm-himix100-linux-
export CC=${CROSS}gcc
export AR=${CROSS}ar

#------------------------------
# 定义平台 (posix or win)
#------------------------------
export OS = posix

#------------------------------
# 全局编译参数变量
#------------------------------
export CFLAGS_ENV=-Wall -Os -ffunction-sections -fdata-sections -std=gnu99 
CFLAGS_ENV += -fno-short-enums -c -fmessage-length=0 -DHOST_TOOLCHAIN -DSL_CATALOG_APP_LOG_PRESENT -DBTMESH \
			-DSL_CATALOG_BTMESH_CONF_PRESENT \
			-DSL_CATALOG_SIMPLE_TIMER_PRESENT \
			-O0 -g3 \
			-DEFR32MG21B010F1024IM32

ifeq ($(OS),posix)
override CFLAGS_ENV += \
-D_DEFAULT_SOURCE \
-D_BSD_SOURCE \
-DPOSIX
endif

ifeq ($(OS),win)
override CFLAGS_ENV += 
endif
#-g 

#-------------------------------
# 全局编译路径变量
#-------------------------------
export ROOT_DIR=$(shell pwd)
export mk_dir=$(pwd)/build/tool
export BUILD_DIR=$(ROOT_DIR)/build/out
export APP_DIR=$(ROOT_DIR)/app
export MOD_DIR=$(ROOT_DIR)/component

#------------------------------
# 生成程序输出路径
#------------------------------
outdir := $(ROOT_DIR)

#----------------------------------------
# 源文件路径(采用相对路径)
#----------------------------------------
srcdir := . src source

#----------------------------------------
# 编译参数
#----------------------------------------
CFLAGS := $(CFLAGS_ENV)

#------------------------------
# 链接参数
#------------------------------
LDFLAGS := -Wl,-Map=$(BUILD_DIR)/object.map,--cref,--gc-section -lpthread
ifeq ($(OS),win)
# Avoid runtime errors because of missing DLLs by using static linking.
LDFLAGS += -static
  # Ws2_32: WinSock library
LDFLAGS += -lWs2_32
endif

ifeq ($(OS), posix)
    # Add -lrt on Linux only
LDFLAGS += -lrt
endif

#------------------------------
# 模块路径
#------------------------------
MOD := $(shell ls $(shell basename $(MOD_DIR)))

#------------------------------
# 移除模块路径下不编译的文件夹
#------------------------------
MOD := $(filter-out app_assert, ${MOD})

#----------------------------------------
# 定义全局头文件路径
#----------------------------------------
export INCLUDE_ENV = $(addprefix -I${MOD_DIR}/, $(MOD)) 
INCLUDE_ENV += $(addsuffix /include, $(addprefix -I${MOD_DIR}/, $(MOD) app_assert))

#----------------------------------------
# 头文件路径
#----------------------------------------
INCLUDE := -I./include 
INCLUDE += $(addprefix -I./, $(srcdir))
INCLUDE += $(INCLUDE_ENV)
INCLUDE += -I${MOD_DIR}/app_assert/include 

#------------------------------
# 链接时需要的库
#------------------------------
LIB += $(foreach d, $(addsuffix /lib, $(addprefix $(MOD_DIR)/, $(MOD))), $(wildcard $(d)/*.a))

#------------------------------
# 不编译的文件,需要指定相对路径
#------------------------------
# nocompile := ./test.cpp

#------------------------------
# 编译前准备
#------------------------------
src := $(foreach d, $(srcdir), $(wildcard $(d)/*.c))
src := $(filter-out ${nocompile} , ${src})
obj := $(src:.c=.o)
dep := $(src:.c=.d)
build := $(BUILD_DIR)/$(subst $(ROOT_DIR)/,,$(CURDIR)/)
objs := $(addprefix $(build)/, $(obj))
deps := $(addprefix $(build)/, $(dep))
target_out := $(outdir)/$(target)

all: $(target_out)

#-----------------------------------------------
# 子模块编译
#-----------------------------------------------
module: ${MOD}

${MOD}: 
	@+make -C ${MOD_DIR}/$@

#-----------------------------------------------
# 生成静态库及依赖关系
#-----------------------------------------------
$(target_out): ${MOD} $(objs)
	@mkdir -p $(addprefix $(build)/, $(srcdir))
	@mkdir -p $(outdir)
	$(CC) -o $(target_out) $(objs) $(INCLUDE) -Xlinker "-(" $(LIB) -Xlinker "-)" $(LDFLAGS)
	@echo "-------------------------------------------"
	@echo "Generate $(target) successs."
	@echo "-------------------------------------------"

-include $(deps)

#-----------------------------------------------
# 生成.o文件的所有依赖关系
#-----------------------------------------------
$(build)/%.o: %.c
	@mkdir -p $(dir $@)
	$(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@

#-----------------------------------------------
# 生成.d文件的所有依赖关系
#-----------------------------------------------
$(build)/%.d: %.c
	@set -e; mkdir -p $(@D); rm -f $@; \
	$(CC) -MM $(CFLAGS) $(INCLUDE) $< > $@.$$$$; \
	sed 's,\($(*F)\)\.o[ :]*,$(build)/$( $@; \
	rm -f $@.$$$$;\

#echo "generate dependencies $(@F) ."

.PHONY: clean clean_all

clean_all:
	-@rm -rf $(BUILD_DIR) 
	@echo "Remove build dir."

#-----------------------------------------------
# 子模块clean
#-----------------------------------------------
MOD_CLEAN = $(addprefix "........clean/", ${MOD})
$(MOD_CLEAN):
	@+make -C ${MOD_DIR}/$(shell basename $@) clean

clean: $(MOD_CLEAN)
	@echo "cleanning ..."
	-@rm -f $(target_out) $(objs)
	@echo "clean completed."

子 Makefile 如下:

#------------------------------
# 生成程序名称
#------------------------------
target := app_log.a

#------------------------------
# 生成程序输出路径
#------------------------------
outdir := lib

#----------------------------------------
# 源文件路径(采用相对路径)
#----------------------------------------
srcdir := . src source

#----------------------------------------
# 头文件路径
#----------------------------------------
INCLUDE := -I./include 
INCLUDE += $(addprefix -I./, $(srcdir))
# INCLUDE += -I../app_assert/include
INCLUDE += $(INCLUDE_ENV)

#----------------------------------------
# 编译参数
#----------------------------------------
CFLAGS := $(CFLAGS_ENV) 
ifeq ($(OS),win)
#override CFLAGS += -DAPP_LOG_NEW_LINE=APP_LOG_NEW_LINE_RN
endif
CFLAGS += -DSL_CATALOG_APP_LOG_PRESENT -DEFR32MG21B010F1024IM32

#------------------------------
# 不编译的文件,需要指定相对路径
#------------------------------
# nocompile := ./test.cpp

#------------------------------
# 编译前准备
#------------------------------
src := $(foreach d, $(srcdir), $(wildcard $(d)/*.c))
src := $(filter-out ${nocompile} , ${src})
obj := $(src:.c=.o)
dep := $(src:.c=.d)
build := $(BUILD_DIR)/$(subst $(ROOT_DIR)/,,$(CURDIR)/)
objs := $(addprefix $(build)/, $(obj))
deps := $(addprefix $(build)/, $(dep))
target_out := $(outdir)/$(target)

all: $(target_out)

#-----------------------------------------------
# 生成静态库及依赖关系
#-----------------------------------------------
$(target_out):$(objs)
	@mkdir -p $(addprefix $(build)/, $(srcdir))
	@mkdir -p $(outdir)
	$(AR) -rc $@ $^
	@echo "-------------------------------------------"
	@echo "create $(target) successs."
	@echo "-------------------------------------------"

-include $(deps)

#-----------------------------------------------
# 生成.o文件的所有依赖关系
#-----------------------------------------------
$(build)/%.o: %.c
	@mkdir -p $(dir $@)
	$(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@

#-----------------------------------------------
# 生成.d文件的所有依赖关系
#-----------------------------------------------
$(build)/%.d: %.c
	@set -e; mkdir -p $(@D); rm -f $@; \
	$(CC) -MM $(CFLAGS) $(INCLUDE) $< > $@.$$$$; \
	sed 's,\($(*F)\)\.o[ :]*,$(build)/$( $@; \
	rm -f $@.$$$$;\

#echo "generate dependencies $(@F) ."

.PHONY: clean clean_all

clean_all:
	-@rm -rf $(BUILD_DIR)

clean:
	@echo "cleanning ..."
	-@rm -f $(target_out) $(objs)
	@echo "clean completed."

3. Makfile 方式 B

和方式A一样,之不过不生成 .a 。由于可能会有同名的 .c 文件,所有生成的 .o 会同名,放到同一个文件夹会导致覆盖,因此,需要在 build 目录下,构建与源文件同目录树的结构,用于存放 .o 。
但是由于子 Makefile 无法向父 Makefile 传递变量,变量只能由父 Makefile 往子 Makefile 中传递。使用 export 定义全局变量,执行子 Makefile 时通过 $(MAKE) -C 执行,可以获得父 Makefile 的变量。这里我们要传递编译好的 .o 的路径给顶层 Makefile ,用于链接生成程序。为了解决这个问题,我们生成一个文件 objs.cache,把所有 编译生成的 .o 文件的路径都存到这个文件中,然后在顶层 Makefile 链接时,读取这个文件的内容,获得所有 .o 文件路径。

顶层 Makefile:

#-----------------------------------------------
# 生成程序名称
#-----------------------------------------------
target := main

#-----------------------------------------------
# 选择编译工具链
#-----------------------------------------------
CROSS =
export CC = ${CROSS}gcc
export AR = ${CROSS}ar

export OS = posix

#-----------------------------------------------
# 全局编译参数变量
#-----------------------------------------------
export CFLAGS_ENV=-Wall -Os -g -ffunction-sections -fdata-sections -std=c99 \
-fno-short-enums -c -fmessage-length=0 -DHOST_TOOLCHAIN -DSL_CATALOG_APP_LOG_PRESENT -DBTMESH \
-DSL_CATALOG_BTMESH_CONF_PRESENT \
-DSL_CATALOG_SIMPLE_TIMER_PRESENT \
-O0 -g3 \
-DEFR32MG21B010F1024IM32 \
-D_DEFAULT_SOURCE \
-D_BSD_SOURCE \
-DPOSIX

#-----------------------------------------------
# 全局编译路径变量
#-----------------------------------------------
export ROOT_DIR = $(shell pwd)
export BUILD_DIR = build/out
export OBJS_CACHE = $(BUILD_DIR)/objs.cache

#-----------------------------------------------
# 定义全局头文件路径
#-----------------------------------------------
export INCLUDE_ENV = -I./ \
-I./component/app_assert/include \
-I./component/app_log/include \
-I./component/app_signal/include \
-I./component/btmesh_conf/include \
-I./component/btmesh_db/include \
-I./component/btmesh_prov/include \
-I./component/ncp_evt_filter/include \
-I./component/ncp_host_bt/include \
-I./component/ncp_host_btmesh/include \
-I./component/simple_timer/include \
-I./component/slist/include 

#-----------------------------------------------
# 生成程序输出路径
#-----------------------------------------------
outdir := $(ROOT_DIR)

#-----------------------------------------------
# 源文件路径(采用相对路径)
#-----------------------------------------------
# src := $(foreach d, $(srcdir), $(wildcard $(d)/*.c))
src := app.c main.c \
app_conf.c \
app_ui.c 

#-----------------------------------------------
# 编译参数
#-----------------------------------------------
CFLAGS := $(CFLAGS_ENV) 

#-----------------------------------------------
# 链接参数
#-----------------------------------------------
LDFLAGS := -Wl,-Map=$(BUILD_DIR)/object.map,--cref,--gc-section \
-lpthread \
-lrt

#-----------------------------------------------
# 模块路径
#-----------------------------------------------
MOD := component/app_log \
component/app_signal \
component/btmesh_conf \
component/btmesh_db \
component/btmesh_prov \
component/ncp_evt_filter \
component/ncp_host_bt \
component/ncp_host_btmesh \
component/simple_timer \
component/slist

#-----------------------------------------------
# 头文件路径
#-----------------------------------------------
INCLUDE := $(INCLUDE_ENV) \
-I./mod1 \
-I./mod1/mod2

#-----------------------------------------------
# 链接时需要的静态库
#-----------------------------------------------
LIB += 

#-----------------------------------------------
# 编译前准备
#-----------------------------------------------
obj := $(src:.c=.o)
dep := $(src:.c=.d)
build := $(BUILD_DIR)
objs := $(addprefix $(build)/, $(obj))
deps := $(addprefix $(build)/, $(dep))
target_out := $(outdir)/$(target)
$(shell mkdir -p $(BUILD_DIR))
$(shell echo "" > $(OBJS_CACHE))

all: $(target_out)
	@echo "All completed."

#-----------------------------------------------
# 子模块编译
#-----------------------------------------------
module: ${MOD}

${MOD}: 
	@echo 
	@+${MAKE} -C $@
	@echo 

#-----------------------------------------------
# 生成程序
#-----------------------------------------------
$(target_out): $(objs) module
	@mkdir -p $(outdir)
	@sort -u $(OBJS_CACHE) -o $(OBJS_CACHE)
	$(CC) -o $(target_out) $(objs) $$(cat $(OBJS_CACHE)) -Xlinker "-(" $(LIB) -Xlinker "-)" $(LDFLAGS)
	@echo "-------------------------------------------"
	@echo "Generate $(target) successs."
	@echo "-------------------------------------------"

-include $(deps)

#-----------------------------------------------
# 生成.o文件的所有依赖关系
#-----------------------------------------------
$(build)/%.o: %.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@

#-----------------------------------------------
# 生成.d文件的所有依赖关系
#-----------------------------------------------
$(build)/%.d: %.c
	@set -e; mkdir -p $(@D); rm -f $@; \
	$(CC) -MM $(CFLAGS) $(INCLUDE) $< > $@.$$$$; \
	sed 's,\($(*F)\)\.o[ :]*,$(build)/$( $@; \
	rm -f $@.$$$$;\
	echo "Generate dependencies $(@F) ."

.PHONY: clean clean_all ${MOD} $(OBJS_CACHE)

#-----------------------------------------------
# 子模块clean
#-----------------------------------------------
MOD_CLEAN = $(addprefix clean_, ${MOD})
$(MOD_CLEAN):
	@echo 
	@+${MAKE} -C $(patsubst clean_%,%,$@) clean
	@echo 

MOD_CLEAN_ALL = $(addprefix clean_all_, ${MOD})
$(MOD_CLEAN_ALL):
	@echo 
	@+${MAKE} -C $(patsubst clean_all_%,%,$@) clean_all
	@echo 

clean: $(MOD_CLEAN)
	@echo "Cleanning ..."
	-@rm -f $(target_out) $(objs) $(OBJS_CACHE)
	@echo "Clean completed."

clean_all: $(MOD_CLEAN_ALL)
	-@rm -rf $(BUILD_DIR) 
	@echo "Remove build dir."

子 Makefile:

#-----------------------------------------------
# 源文件路径(采用相对路径)
#-----------------------------------------------
# src := $(foreach d, $(srcdir), $(wildcard $(d)/*.c))
src := app_log_cli.c \
app_log.c \
sl_iostream_handles.c

#-----------------------------------------------
# 获取当前路径和顶层目录的相对路径
#-----------------------------------------------
ROOT_DIR ?= .
OBJS_CACHE ?= objs.cache
curdir := $(shell realpath --relative-to $(ROOT_DIR) .)
path := $(shell realpath --relative-to . $(ROOT_DIR))

#-----------------------------------------------
# 头文件路径
#-----------------------------------------------
INCLUDE := $(addprefix -I$(path)/, $(foreach d, $(INCLUDE_ENV), $(patsubst -I%,%,$(d)))) \
-I./

#-----------------------------------------------
# 编译参数
#-----------------------------------------------
CFLAGS := $(CFLAGS_ENV) \
-DSL_CATALOG_APP_LOG_PRESENT -DEFR32MG21B010F1024IM32

#-----------------------------------------------
# 添加子模块
#-----------------------------------------------
MOD := 

#-----------------------------------------------
# 编译前准备,获取当前路径名称和顶层目录的相对路径
#-----------------------------------------------
obj := $(src:.c=.o)
dep := $(src:.c=.d)
src_objs := $(addprefix $(BUILD_DIR)/$(curdir)/, $(obj))
build := $(path)/$(BUILD_DIR)/$(curdir)
objs := $(addprefix $(build)/, $(obj))
deps := $(addprefix $(build)/, $(dep))
$(shell echo $(src_objs) >> $(path)/$(OBJS_CACHE))

all: module $(objs)

module: ${MOD}

${MOD}:
	@echo 
	@+${MAKE} -C $@
	@echo 

-include $(deps)

#-----------------------------------------------
# 生成.o文件的所有依赖关系
#-----------------------------------------------
$(build)/%.o: %.c
	@mkdir -p $(dir $@)
	@# @echo $(addprefix $(BUILD_DIR)/$(curdir)/, $(shell basename $@)) >> $(path)/$(OBJS_CACHE)
	$(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@

#-----------------------------------------------
# 生成.d文件的所有依赖关系
#-----------------------------------------------
$(build)/%.d: %.c
	@set -e; mkdir -p $(@D); rm -f $@; \
	$(CC) -MM $(CFLAGS) $(INCLUDE) $< > $@.$$$$; \
	sed 's,\($(*F)\)\.o[ :]*,$(build)/$( $@; \
	rm -f $@.$$$$;\
	echo "generate dependencies $(@F) ."

.PHONY: clean clean_all $(MOD)

#-----------------------------------------------
# 子模块clean
#-----------------------------------------------
MOD_CLEAN = $(addprefix clean_, ${MOD})
$(MOD_CLEAN):
	@echo 
	@+${MAKE} -C $(patsubst clean_%,%,$@) clean
	@echo 

MOD_CLEAN_ALL = $(addprefix clean_all_, ${MOD})
$(MOD_CLEAN_ALL):
	@echo 
	@+${MAKE} -C $(patsubst clean_all_%,%,$@) clean_all
	@echo 

clean: $(MOD_CLEAN)
	@echo "Cleanning ..."
	-@rm -f $(objs)
	@echo "Clean completed."

clean_all: $(MOD_CLEAN_ALL)
	-@rm -f $(deps) $(objs) $(OBJS_CACHE)
	@echo "Clean all."

方式A 和 方式B 的 Makefile 有一个问题,由于子模块是虚拟目标,导致每次都会遍历一遍子模块,都会导致重新链接。

4. Makefile 方式 C

使用方式1实现来实现,把所有的 .c 都添加到顶层 Makefile 中。

顶层 Makefile:

#-----------------------------------------------
# 生成程序名称
#-----------------------------------------------
target := main

#-----------------------------------------------
# 选择编译工具链
#-----------------------------------------------
CROSS =
export CC = ${CROSS}gcc
export AR = ${CROSS}ar

export OS = posix

#-----------------------------------------------
# 全局编译参数变量
#-----------------------------------------------
export CFLAGS_ENV=-Wall -Os -g -ffunction-sections -fdata-sections -std=c99 -fPIC \
-fno-short-enums -c -fmessage-length=0 -DHOST_TOOLCHAIN -DSL_CATALOG_APP_LOG_PRESENT -DBTMESH \
-DSL_CATALOG_BTMESH_CONF_PRESENT \
-DSL_CATALOG_SIMPLE_TIMER_PRESENT \
-O0 -g3 \
-DEFR32MG21B010F1024IM32 \
-D_DEFAULT_SOURCE \
-D_BSD_SOURCE \
-DPOSIX

#-----------------------------------------------
# 全局编译路径变量
#-----------------------------------------------
export ROOT_DIR = $(shell pwd)
export BUILD_DIR = $(ROOT_DIR)/build/out
export OBJS_CACHE = $(BUILD_DIR)/objs.cache

#-----------------------------------------------
# 定义全局头文件路径,使用绝对路径
#-----------------------------------------------
export INCLUDE_ENV = -I$(ROOT_DIR) \
-I$(ROOT_DIR)/component/app_assert/include \
-I$(ROOT_DIR)/component/app_log/include \
-I$(ROOT_DIR)/component/app_signal/include \
-I$(ROOT_DIR)/component/btmesh_conf/include \
-I$(ROOT_DIR)/component/btmesh_db/include \
-I$(ROOT_DIR)/component/btmesh_prov/include \
-I$(ROOT_DIR)/component/ncp_evt_filter/include \
-I$(ROOT_DIR)/component/ncp_host_bt/include \
-I$(ROOT_DIR)/component/ncp_host_btmesh/include \
-I$(ROOT_DIR)/component/simple_timer/include \
-I$(ROOT_DIR)/component/slist/include 

#-----------------------------------------------
# 生成程序输出路径,使用绝对路径
#-----------------------------------------------
outdir := $(ROOT_DIR)

#-----------------------------------------------
# 源文件路径,使用相对路径
#-----------------------------------------------
# srcs := $(foreach d, $(srcdir), $(wildcard $(d)/*.c))
src := main.c app.c app_ui.c app_conf.c

src_path = component/app_log
src += $(src_path)/app_log_cli.c \
$(src_path)/app_log.c \
$(src_path)/sl_iostream_handles.c

src_path = component/app_signal
src += $(src_path)/app_signal_posix.c 

src_path = component/btmesh_conf
src += $(src_path)/btmesh_conf.c \
$(src_path)/btmesh_conf_distributor.c \
$(src_path)/btmesh_conf_executor.c \
$(src_path)/btmesh_conf_job.c \
$(src_path)/btmesh_conf_task.c

src_path = component/btmesh_db
src += $(src_path)/btmesh_db.c

src_path = component/btmesh_prov
src += $(src_path)/btmesh_prov.c

src_path = component/ncp_evt_filter
src += $(src_path)/sl_ncp_evt_filter.c

src_path = component/ncp_host_bt
src += $(src_path)/app_sleep.c \
$(src_path)/named_socket.c \
$(src_path)/sl_btmesh.c \
$(src_path)/sl_btmesh_serdeser.c \
$(src_path)/sl_bt_ncp_host.c \
$(src_path)/system.c \
$(src_path)/host_comm.c \
$(src_path)/ncp_host.c \
$(src_path)/sl_btmesh_lib.c \
$(src_path)/sl_bt_ncp_host_api.c \
$(src_path)/sl_malloc.c \
$(src_path)/tcp_posix.c \
$(src_path)/uart_posix.c

src_path = component/ncp_host_btmesh
src += $(src_path)/sl_btmesh_ncp_host_api.c \
$(src_path)/sl_btmesh_ncp_host.c

src_path = component/simple_timer
src += $(src_path)/sl_simple_timer_posix.c

src_path = component/slist
src += $(src_path)/sl_slist.c

#-----------------------------------------------
# 编译参数
#-----------------------------------------------
CFLAGS := $(CFLAGS_ENV) \
-DSL_CATALOG_SIMPLE_TIMER_PRESENT \
-DBTMESH \
-DSL_CATALOG_BTMESH_CONF_PRESENT \
-DSL_CATALOG_APP_LOG_PRESENT -DEFR32MG21B010F1024IM32

#-----------------------------------------------
# 链接参数
#-----------------------------------------------
LDFLAGS := -Wl,-Map=$(BUILD_DIR)/object.map,--cref,--gc-section -fPIC \
-lpthread \
-lrt

#-----------------------------------------------
# 模块路径
#-----------------------------------------------
MOD := 

#-----------------------------------------------
# 头文件路径,使用绝对路径
#-----------------------------------------------
INCLUDE := $(INCLUDE_ENV) 

#-----------------------------------------------
# 链接时需要的静态库
#-----------------------------------------------
LIB += 

#-----------------------------------------------
# 编译前准备
#-----------------------------------------------
obj := $(src:.c=.o)
dep := $(src:.c=.d)
build := $(BUILD_DIR)
objs := $(addprefix $(build)/, $(obj))
deps := $(addprefix $(build)/, $(dep))
target_out := $(outdir)/$(target)
$(shell mkdir -p $(BUILD_DIR))
$(shell echo "" > $(OBJS_CACHE))

all: $(target_out)
	@echo "All completed."

#-----------------------------------------------
# 子模块编译
#-----------------------------------------------
module: ${MOD}

${MOD}: 
	@echo 
	@+${MAKE} -C $@
	@echo 

#-----------------------------------------------
# 生成程序
#-----------------------------------------------
$(target_out): $(objs) module
	@mkdir -p $(outdir)
	@sort -u $(OBJS_CACHE) -o $(OBJS_CACHE)
	$(CC) -o $(target_out) $(objs) $$(cat $(OBJS_CACHE)) -Xlinker "-(" $(LIB) -Xlinker "-)" $(LDFLAGS)
	@echo "-------------------------------------------"
	@echo "Generate $(target) successs."
	@echo "-------------------------------------------"

-include $(deps)

#-----------------------------------------------
# 生成.o文件的所有依赖关系
#-----------------------------------------------
$(build)/%.o: %.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(INCLUDE) -c $(ROOT_DIR)/$< -o $@ 

#-----------------------------------------------
# 生成.d文件的所有依赖关系
#-----------------------------------------------
$(build)/%.d: %.c
	@set -e; mkdir -p $(@D); rm -f $@; \
	$(CC) -MM $(CFLAGS) $(INCLUDE) $< > $@.$$$$; \
	sed 's,\($(*F)\)\.o[ :]*,$(build)/$( $@; \
	rm -f $@.$$$$;\
	echo "generate dependencies $(@F) ."

.PHONY: clean clean_all ${MOD} $(OBJS_CACHE)

#-----------------------------------------------
# 子模块clean
#-----------------------------------------------
MOD_CLEAN = $(addprefix clean_, ${MOD})
$(MOD_CLEAN):
	@echo 
	@+${MAKE} -C $(patsubst clean_%,%,$@) clean
	@echo 

MOD_CLEAN_ALL = $(addprefix clean_all_, ${MOD})
$(MOD_CLEAN_ALL):
	@echo 
	@+${MAKE} -C $(patsubst clean_all_%,%,$@) clean_all
	@echo 

clean: $(MOD_CLEAN)
	@echo "Cleanning ..."
	-@rm -f $(target_out) $(objs) $(OBJS_CACHE)
	@echo "Clean completed."

clean_all: $(MOD_CLEAN_ALL)
	-@rm -rf $(BUILD_DIR) $(target_out)
	@echo "Remove build dir."

5. Makefile 方式 D

也是把所有的 .c 文件放到顶层 Makefile 中进行编译。这里经过测试,变量形式添加 .c,和直接写出相对路径没有区别。这里还需要将编译依赖的目标路径修改为当前的,但是输出路径修改为 build 目录下的。
这里主要是为了测试,添加变量是否会对其产生明显影响。实际测试,并没有太大影响。但是这里修改后,会导致每次都重新生成 .d 文件,因此该修改是不合理的,这里只是为了测试验证编译速度。

顶层 Makefile:

#-----------------------------------------------
# 生成程序名称
#-----------------------------------------------
target := main

#-----------------------------------------------
# 选择编译工具链
#-----------------------------------------------
CROSS =
export CC = ${CROSS}gcc
export AR = ${CROSS}ar

export OS = posix

#-----------------------------------------------
# 全局编译参数变量
#-----------------------------------------------
export CFLAGS_ENV=-Wall -Os -g -ffunction-sections -fdata-sections -std=c99 -fPIC \
-fno-short-enums -c -fmessage-length=0 -DHOST_TOOLCHAIN -DSL_CATALOG_APP_LOG_PRESENT -DBTMESH \
-DSL_CATALOG_BTMESH_CONF_PRESENT \
-DSL_CATALOG_SIMPLE_TIMER_PRESENT \
-O0 -g3 \
-DEFR32MG21B010F1024IM32 \
-D_DEFAULT_SOURCE \
-D_BSD_SOURCE \
-DPOSIX

#-----------------------------------------------
# 全局编译路径变量
#-----------------------------------------------
export ROOT_DIR = $(shell pwd)
export BUILD_DIR = $(ROOT_DIR)/build/out
export OBJS_CACHE = $(BUILD_DIR)/objs.cache

#-----------------------------------------------
# 定义全局头文件路径,使用绝对路径
#-----------------------------------------------
export INCLUDE_ENV = -I$(ROOT_DIR) \
-I$(ROOT_DIR)/component/app_assert/include \
-I$(ROOT_DIR)/component/app_log/include \
-I$(ROOT_DIR)/component/app_signal/include \
-I$(ROOT_DIR)/component/btmesh_conf/include \
-I$(ROOT_DIR)/component/btmesh_db/include \
-I$(ROOT_DIR)/component/btmesh_prov/include \
-I$(ROOT_DIR)/component/ncp_evt_filter/include \
-I$(ROOT_DIR)/component/ncp_host_bt/include \
-I$(ROOT_DIR)/component/ncp_host_btmesh/include \
-I$(ROOT_DIR)/component/simple_timer/include \
-I$(ROOT_DIR)/component/slist/include 

#-----------------------------------------------
# 生成程序输出路径,使用绝对路径
#-----------------------------------------------
outdir := $(ROOT_DIR)

#-----------------------------------------------
# 源文件路径,使用相对路径
#-----------------------------------------------
# srcs := $(foreach d, $(srcdir), $(wildcard $(d)/*.c))
src := main.c app.c app_ui.c app_conf.c \
component/app_log/app_log_cli.c \
component/app_log/app_log.c \
component/app_log/sl_iostream_handles.c \
component/app_signal/app_signal_posix.c \
component/btmesh_conf/btmesh_conf.c \
component/btmesh_conf/btmesh_conf_distributor.c \
component/btmesh_conf/btmesh_conf_executor.c \
component/btmesh_conf/btmesh_conf_job.c \
component/btmesh_conf/btmesh_conf_task.c \
component/btmesh_db/btmesh_db.c \
component/btmesh_prov/btmesh_prov.c \
component/ncp_evt_filter/sl_ncp_evt_filter.c \
component/ncp_host_bt/app_sleep.c \
component/ncp_host_bt/named_socket.c \
component/ncp_host_bt/sl_btmesh.c \
component/ncp_host_bt/sl_btmesh_serdeser.c \
component/ncp_host_bt/sl_bt_ncp_host.c \
component/ncp_host_bt/system.c \
component/ncp_host_bt/host_comm.c \
component/ncp_host_bt/ncp_host.c \
component/ncp_host_bt/sl_btmesh_lib.c \
component/ncp_host_bt/sl_bt_ncp_host_api.c \
component/ncp_host_bt/sl_malloc.c \
component/ncp_host_bt/tcp_posix.c \
component/ncp_host_bt/uart_posix.c \
component/ncp_host_btmesh/sl_btmesh_ncp_host_api.c \
component/ncp_host_btmesh/sl_btmesh_ncp_host.c \
component/simple_timer/sl_simple_timer_posix.c \
component/slist/sl_slist.c

#-----------------------------------------------
# 编译参数
#-----------------------------------------------
CFLAGS := $(CFLAGS_ENV) \
-DSL_CATALOG_SIMPLE_TIMER_PRESENT \
-DBTMESH \
-DSL_CATALOG_BTMESH_CONF_PRESENT \
-DSL_CATALOG_APP_LOG_PRESENT -DEFR32MG21B010F1024IM32

#-----------------------------------------------
# 链接参数
#-----------------------------------------------
LDFLAGS := -Wl,-Map=$(BUILD_DIR)/object.map,--cref,--gc-section -fPIC \
-lpthread \
-lrt

#-----------------------------------------------
# 模块路径
#-----------------------------------------------
MOD := 

#-----------------------------------------------
# 头文件路径,使用绝对路径
#-----------------------------------------------
INCLUDE := $(INCLUDE_ENV) 

#-----------------------------------------------
# 链接时需要的静态库
#-----------------------------------------------
LIB += 

#-----------------------------------------------
# 编译前准备
#-----------------------------------------------
obj := $(src:.c=.o)
dep := $(src:.c=.d)
build := $(BUILD_DIR)
# objs := $(addprefix $(build)/, $(obj))
# deps := $(addprefix $(build)/, $(dep))
objs := $(obj)
deps := $(dep)
target_out := $(outdir)/$(target)
$(shell mkdir -p $(BUILD_DIR))
$(shell echo "" > $(OBJS_CACHE))

all: $(target_out)
	@echo "All completed."

#-----------------------------------------------
# 子模块编译
#-----------------------------------------------
module: ${MOD}

${MOD}: 
	@echo 
	@+${MAKE} -C $@
	@echo 

#-----------------------------------------------
# 生成程序
#-----------------------------------------------
$(target_out): $(objs) module
	@mkdir -p $(outdir)
	@sort -u $(OBJS_CACHE) -o $(OBJS_CACHE)
	$(CC) -o $(target_out) $(addprefix $(build)/,$(objs)) $$(cat $(OBJS_CACHE)) -Xlinker "-(" $(LIB) -Xlinker "-)" $(LDFLAGS)
	@echo "-------------------------------------------"
	@echo "Generate $(target) successs."
	@echo "-------------------------------------------"

-include $(deps)

#-----------------------------------------------
# 生成.o文件的所有依赖关系
#-----------------------------------------------
%.o: %.c
	@mkdir -p $(dir $(build)/$@)
	$(CC) $(CFLAGS) $(INCLUDE) -c $(ROOT_DIR)/$< -o $(build)/$@ 

#-----------------------------------------------
# 生成.d文件的所有依赖关系
#-----------------------------------------------
%.d: %.c
	@set -e; mkdir -p $(build)/$(@D); rm -f $(build)/$@; \
	$(CC) -MM $(CFLAGS) $(INCLUDE) $< > $@.$$$$; \
	sed 's,\($(*F)\)\.o[ :]*,$(build)/$( $(build)/$@; \
	rm -f $@.$$$$;\
	echo "Generate dependencies $(@F) ."

.PHONY: clean clean_all ${MOD} $(OBJS_CACHE)

#-----------------------------------------------
# 子模块clean
#-----------------------------------------------
MOD_CLEAN = $(addprefix clean_, ${MOD})
$(MOD_CLEAN):
	@echo 
	@+${MAKE} -C $(patsubst clean_%,%,$@) clean
	@echo 

MOD_CLEAN_ALL = $(addprefix clean_all_, ${MOD})
$(MOD_CLEAN_ALL):
	@echo 
	@+${MAKE} -C $(patsubst clean_all_%,%,$@) clean_all
	@echo 

clean: $(MOD_CLEAN)
	@echo "Cleanning ..."
	-@rm -f $(target_out) $(addprefix $(build)/,$(objs)) $(OBJS_CACHE)
	@echo "Clean completed."

clean_all: $(MOD_CLEAN_ALL)
	-@rm -rf $(BUILD_DIR) $(target_out)
	@echo "Remove build dir."

6. 编译测试

由于多次进行相同操作的编译,编译时间基本保持不变,因此,只提供一组时间数据做为参考即可。

不启用多线程编译,使用 make -j1

编译方式 编译完后clean_all 编译完后clean clean_all后编译 clean后编译 二次编译
方式A 0.0039s 0.289s 3.775s 2.844s 0.309s
方式B 0.197s 0.227s 3.750s 2.736s 0.292s
方式C 0.072s 0.072s 5.076s 3.381s 0.223s
方式D 0.072s 0.072s 5.076s 3.381s 0.223s

针对单线程编译,使用子 Makefile 时,在clean后编译是有优势的,但是二次编译时,由于会遍历所有的子模块的Makefile,会浪费掉一些时间,如果子模块足够多,这个时间会相应的加长。

启用多线程编译,使用 make -j12

编译方式 编译完后clean_all 编译完后clean clean_all后编译 clean后编译 二次编译
方式A 0.040s 0.221s 0.988s 0.752s 0.289s
方式B 0.059s 0.066s 0.885s 0.672s 0.178s
方式C 0.076s 0.072s 2.608s 1.432s 0.206s
方式D 0.984s 1.004s 2.492s 2.424s 2.379s

针对多线程编译,方式D 只有 “clean_all后编译” 数据有参考价值。其他操作都会因为重新生成 .d 而损失效率,没有参考价值。

综上,方式B 编写的 Makefile 效率最高,从而,通过编写子 Makefile,来链式调用 Makefile 执行,能够极大提高效率。

通过编译输出信息,我们可以推断出,当使用多线程编译时,Makefile 会把能够同时执行的不相关的任务,使用多线程进行编译操作,而执行子 Makefile ,相当于是开启新进程来执行 Makefile,不会和父 Makefile 有任何操作上的关联。那么为什么执行子 Makefile 会比全部通过顶层 Makefile 编译速度快呢?也就是为什么多线程会比多进程慢呢?还是看输出信息,可以得出,Makefile 会在生成所有 .d 文件后,才去执行编译 .o 的命令,因此全写在一个 Makefile 中,会生成完所有 .d 才编译,通过写子 Makefile 的方式,每个 Makefile 不相关,虽然还是会生成完所有 .d 才编译,但是这是对于每个子 Makefile 来说的,因此,相当于,编译和生成 .d 是在进程角度是同时进行的。这是使用子 Makefile 比只用顶层 Makefile 快的一个原因。另外,通过 “clean后编译” 的结果,可以看出,当没有 .d 文件生成的情况下,使用子 Makefile 编译,就是比使用一个顶层 Makefile 的编译快,而且是快很多。 因此,最佳方式就是使用方式B来构建编译架构。

使用子 Makefile ,还有一个优势,针对上述的 Makefile,可以对子模块进行单独的编译和clean,可以依赖顶层 Makefile 进行操作,也可以不依赖顶层 Makefile,单独进行编译和clean。这种方式,可以针对单个模块进行编译,在大型工程下,能够不编译整个工程,只编译模块,然后再链接,有效的节约时间。另外,上述 Makefile 还支持 TABLE 键提示补全操作。

依赖顶层Makefile的编译和clean

依赖顶层 Makefile 的编译和clean,会继承顶层 Makefile 的全局变量,包括工具链。

编译:
Makefile 优化编译速度_第1张图片

clean:
Makefile 优化编译速度_第2张图片

不依赖顶层Makefile的编译

不依赖顶层 Makefile,需要在子 Makefile 的目录下进行操作,它不会顶层 Makefile 中的工具链,会从系统环境变量中继承工具链,编译只会输出 .o 文件。有需要可以自己添加工具链到子 Makefile 中。

编译:
Makefile 优化编译速度_第3张图片

本测试的工程,只有两层 Makefile,还可以再在子 Makefile 下再添加模块,套娃,实现的效果和当前是一样的。

你可能感兴趣的:(c语言)