Makefile 优化编译速度

news2024/11/25 16:00:22

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)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
	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)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
	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)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
	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)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
	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)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
	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)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $(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编译完后cleanclean_all后编译clean后编译二次编译
方式A0.0039s0.289s3.775s2.844s0.309s
方式B0.197s0.227s3.750s2.736s0.292s
方式C0.072s0.072s5.076s3.381s0.223s
方式D0.072s0.072s5.076s3.381s0.223s

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

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

编译方式编译完后clean_all编译完后cleanclean_all后编译clean后编译二次编译
方式A0.040s0.221s0.988s0.752s0.289s
方式B0.059s0.066s0.885s0.672s0.178s
方式C0.076s0.072s2.608s1.432s0.206s
方式D0.984s1.004s2.492s2.424s2.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 的全局变量,包括工具链。

编译:
请添加图片描述

clean:
请添加图片描述

不依赖顶层Makefile的编译

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

编译:
请添加图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1049603.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用ElementUI结合Mock完成主页的搭建

目录 一、Mock ( 1 ) 讲述 ( 2 ) 作用 二、引用 三、主页搭建 学习后带来的收获 一、Mock ( 1 ) 讲述 Mock.js是一个用于前端开发中模拟数据的库。它可以帮助开发人员在前端开发过程中模拟接口返回的数据&#xff0c;从而实现前后端分离开发。Mock.js提供了一套简单易…

优维产品最佳实践:实例视图

背 景 模型可以定义很多的字段&#xff0c;当这些字段越来越多的时候&#xff0c;直接打开实例页面&#xff0c;会杂乱无章的呈现出来&#xff0c;对于用户来说无法快速的找到想要的信息&#xff0c;也不便于查看数据。而且并不是所有的字段都一定会录入了数据&#xff0c;常常…

软件测试/测试开发丨结对编程助手 GitHubCopilot

点此获取更多相关资料 简介 GitHub Copilot 是一款 AI 结对程序员&#xff0c;可帮助您更快、更少地编写代码。GitHub Copilot 由 GitHub、OpenAI 和 Microsoft 开发的生成式 AI 模型提供支持。它可作为 Visual Studio Code、Visual Studio、Neovim 和 JetBrains 集成开发环境…

Vovsoft Text Edit Plus 专业文本编辑器工具软件:简洁高效的创作利器

作为一名专业软件评测人员&#xff0c;我有幸使用了一款备受赞誉的文本编辑器工具软件——Vovsoft Text Edit Plus。在这篇评测中&#xff0c;我将客观、细致地分析它的实用性和使用场景&#xff0c;同时揭示它的优缺点&#xff0c;帮助您更好地了解这款软件。 第一部分&#x…

ROS2 从头开始:第 4 部分 - 使用 ROS2 组合构建强大的机器人系统

一、说明 组件编程是软件规模庞大后,有限的程序个体不能满足系统的动态扩张的系统设计方案。组件编程的首要用途就是计算机操作系统。而ROS2也是操作系统,动态扩大或缩减是必然存在的,因此需要组件实现。 组件编程的其它参考资料: 【BOOST C++】组件编程(…

01-http概述

HTTP概述 HTTP使用的是可靠地数据传输协议。HTTP属于应用层协议 Web客户端和服务器 web服务器&#xff1a;又称http服务器&#xff0c;用于存储web内容&#xff0c;并向web客户端提供web内容web客户端&#xff1a;用于请求web服务器的应用软件&#xff0c;常见为浏览器 资源…

Java实现word excel ppt模板渲染与导出及预览 LibreOffice jodconverter

Java Office 一、文档格式转换 文档格式转换是office操作中经常需要进行一个操作&#xff0c;例如将docx文档转换成pdf格式。 java在这方面有许多的操作方式&#xff0c;大致可以分为内部调用&#xff08;无需要安装额外软件&#xff09;&#xff0c;外部调用&#xff08;需…

SW线光源是真实的(点光源)

点光源在渲染下真实 点光源地板反射是对的

uni-app:获取元素宽高

效果 代码 这里我定义的宽为500px,高为200排序,控制台输出的结果是502,202。原因是我设置了上下左右宽度各为1px的border边框导致 核心代码分析 // const query uni.createSelectorQuery();表示创建了一个选择器查询实例。通过这个实例&#xff0c;你可以使用不同的方法来选择…

MYSQL——二、理论基础

Mysql 一、什么是数据库二、数据库管理系统(DBMS)三、数据库与文件系统的区别四、数据库技术构成及发展史1、技术构成2、发展史 五、常见数据库1、关系型数据库2、ACID原则3、关系型数据库的优缺点4、非关系型数据库5、非关系数据库的优点6、DBMS支持的数据模型7、运维对数据库…

C#中的for和foreach的探究与学习

一:语句及表示方法 for语句: for(初始表达式;条件表达式;增量表达式) {循环体 }foreach语句: foreach(数据类型 变量 in 数组或集合) {循环体 }理解 1.从程序逻辑上理解,foreach是通过指针偏移实现的(最初在-1位置,每循环一次,指针就便宜一个单位),而for循环是通

mac有必要用清理软件吗?有哪些免费的清理工具

当我们谈到Mac电脑时&#xff0c;很多人都会觉得它比Windows系统更加稳定和高效&#xff0c;也更不容易积累垃圾文件。但实际上&#xff0c;任何长时间使用的操作系统都会逐渐积累不必要的文件和缓存。那么&#xff0c;对于Mac用户来说&#xff0c;有必要使用专门的清理软件吗&…

十九,镜面IBL--BRDF积分贴图

再回顾下镜面部分的分割求和近似法 现在关注第二部分 最后可化为 也就是说&#xff0c;这两部分积分可以获得F0的系数和F0的偏差。 这两个值可以存储到BRDF积分贴图的RG部分。void main() { vec2 integratedBRDF IntegrateBRDF(TexCoords.x, TexCoords.y); FragColor …

react create-react-app v5 从零搭建(使用 npm run eject)

前言&#xff1a; 好久没用 create-react-app做项目了&#xff0c;这次为了个h5项目&#xff0c;就几个页面&#xff0c;决定自己搭建一个&#xff08;ps:mmp 好久没用&#xff0c;搭建的时候遇到一堆问题&#xff09;。 我之前都是使用 umi 。后台管理系统的项目 使用 antd-…

嵌入式Linux应用开发-第七章-野火-正点原子IMX6ULL的LED驱动程序

嵌入式Linux应用开发-第七章-野火-正点原子IMX6ULL的LED驱动程序 野火IMX6ULL的LED驱动程序7.4 野火/正点原子 IMX6ULL的 LED驱动程序7.4.1 原理图7.4.1.1 野火 fire_imx6ull-pro开发板7.4.1.2 正点原子 Atk_imx6ull-alpha开发板 7.4.2 所涉及的寄存器操作7.4.2.1 野火 fire_im…

2011 款宝马 318i 车发动机怠速游车、抖动

故障现象 一辆2011款宝马318i车&#xff0c;搭载N46B20E发动机&#xff0c;累计行驶里程约为9.5万km。该车因发动机怠速游车、抖动在其他维修厂维修&#xff0c;维修人员更换了火花塞、点火线圈、空气流量传感器、VANOS&#xff08;可变气门正时系统&#xff09;电磁阀、气门升…

竞赛无人机搭积木式编程(四)---2023年TI电赛G题空地协同智能消防系统(无人机部分)

竞赛无人机搭积木式编程&#xff08;四&#xff09; ---2023年TI电赛G题空地协同智能消防系统&#xff08;无人机部分&#xff09; 无名小哥 2023年9月15日 赛题分析与解题思路综述 飞控用户在学习了TI电赛往届真题开源方案以及用户自定义航点自动飞行功能方案讲解后&#x…

delphi 11 安装失败

delphi 11 安装遇到如下图&#xff1a; 解决方法&#xff1a; 以管理员身份重新安装&#xff01;&#xff01;&#xff01; 以管理员身份重新安装&#xff01;&#xff01;&#xff01; 以管理员身份重新安装&#xff01;&#xff01;&#xff01; 管理员身份&#xff01;&…

什么是数学建模(mooc笔记)

什么是数学建模 前提&#xff1a;我们数学建模国赛计划选择C题&#xff0c;故希望老师的教学中侧重与C题相关性大的模型及其思想进行培训。之后的学习内容中希望涉及以下知识点&#xff1a; logistic回归相关知识点。如&#xff1a;用法、适用、限制范围等。精学数学建模中常…

Kafka:介绍和内部工作原理

展示Kafka工作方式的简单架构。 什么是Kafka&#xff1f;为什么我们要使用它&#xff1f;它是消息队列吗&#xff1f; 它是一个分布式流处理平台或分布式提交日志。 Kafka通常用于实时流数据管道&#xff0c;即在系统之间传输数据&#xff0c;构建不断流动的数据转换系统和构…