刚开始接触linux的时候,编译应用层经常使用Makefile,每次新增和删除.c文件都要在Makefile中重新指定,有时候还要修改头文件和库文件路径,当时会写很多可执行程序,难忍其烦,所以利用find指令构建了一个可以自动搜索.c,.cpp或者自定义后缀的文件,搜索.h,.so, .a。(尽管cmake也有这个功能,但是cmake自己也很麻烦)
首先提供一个linux下同时编译.c和.cpp的,链接成opencv可执行程序,所使用的Makefile
# linux下使用/bin/dash,echo -e 会将 -e 输出,windows下可删除此行
SHELL = /bin/bash
# 定义目标文件名称
BUILD_DIR := ./build/
TARGET := ./execute
# 定义工具链路径和编译器路径
TOOLCHAIN_DIR := /usr/
COMPILER_PATH := $(TOOLCHAIN_DIR)bin/
# 定义编译器和链接器命令和标志
CC := $(COMPILER_PATH)gcc
CX := $(COMPILER_PATH)g++
LD := $(COMPILER_PATH)g++
SZ := $(COMPILER_PATH)size
OBJCOPY := $(COMPILER_PATH)objcopy
CC_MARK := .xc
CX_MARK := .xc++
CC_FLAG := -xc -g
CX_FLAG := -xc++ -g
LD_FLAG :=
HEAD_PATH := -I$(TOOLCHAIN_DIR)/include/ -I/usr/local/include/opencv4/
LIB_PATH := -L$(TOOLCHAIN_DIR)/lib/ -L/usr/local/lib/
LIB_FLAG := -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_imgcodecs
SRC_LIB :=
SRC_CC :=
SRC_CX :=
EXCLUDE_FILES :=
# 设置源文件列表
HEAD_TYPE := .h .hpp .hh
LIB_TYPE := .a .so
CC_TYPE := .c
CX_TYPE := .cpp .cc .cu
HEAD_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(HEAD_TYPE)))
LIB_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(LIB_TYPE)))
CC_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(CC_TYPE)))
CX_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(CX_TYPE)))
LOCAL_HEAD := $(shell find . -type f -regex ".*\.\($(HEAD_TYPE_SIFT)\)" -printf "%P ")
LOCAL_LIB := $(shell find . -type f -regex ".*\.\($(LIB_TYPE_SIFT)\)" -printf "%P ")
LOCAL_CC := $(shell find . -type f -regex ".*\.\($(CC_TYPE_SIFT)\)" -printf "%P ")
LOCAL_CX := $(shell find . -type f -regex ".*\.\($(CX_TYPE_SIFT)\)" -printf "%P ")
HEAD_PATH += $(addprefix -I,$(sort $(dir $(filter-out $(EXCLUDE_FILES),$(LOCAL_HEAD)))))
SRC_LIB += $(filter-out $(EXCLUDE_FILES),$(LOCAL_LIB))
SRC_CC += $(filter-out $(EXCLUDE_FILES),$(LOCAL_CC))
SRC_CX += $(filter-out $(EXCLUDE_FILES),$(LOCAL_CX))
# 生成对象文件和依赖文件的列表
OBJ_CC := $(addprefix $(BUILD_DIR),$(notdir $(SRC_CC:%=%$(CC_MARK).o)))
OBJ_CX := $(addprefix $(BUILD_DIR),$(notdir $(SRC_CX:%=%$(CX_MARK).o)))
# 生成源文件查找路径
vpath % $(sort $(dir $(SRC_CC))) $(sort $(dir $(SRC_CX)))
# 定义all依赖
all : $(TARGET)
@echo " CHECK $<"
@$(SZ) $<
# 定义编译和链接命令和规则
$(TARGET): $(OBJ_CC) $(OBJ_CX) $(SRC_LIB)
@echo " LN $^ -> $@"
@$(LD) $(LD_FLAG) -o $@ $^ $(LIB_PATH) $(LIB_FLAG)
$(BUILD_DIR)%$(CC_MARK).o : % Makefile | $(BUILD_DIR)
@echo " CC $<"
@$(CC) $(CC_FLAG) $(HEAD_PATH) -MMD -MP -MF"$(@:%.o=%.d)" -c $< -o $@
$(BUILD_DIR)%$(CX_MARK).o : % Makefile | $(BUILD_DIR)
@echo " CX $<"
@$(CX) $(CX_FLAG) $(HEAD_PATH) -MMD -MP -MF"$(@:%.o=%.d)" -c $< -o $@
$(BUILD_DIR) :
@echo " MK $@"
@mkdir $@
# 包含依赖文件
-include $(wildcard $(BUILD_DIR)*.d)
# 生成compile_commands.json文件的规则
json: $(SRC_CC) $(SRC_CX) Makefile
@echo "Generating compile_commands.json"
@rm -f compile_commands.json
@echo "[" > compile_commands.json
@for file in $(SRC_CC); do \
compile_cmd="$(CC) $(CC_FLAG) $(HEAD_PATH) -c $${file} -o $${file}.$(CC_MARK).o" \
command_json=" { \"arguments\": [ \"$$compile_cmd\" ], \"directory\": \"${PWD}\", \"file\": \"$$file\" },"; \
echo -e "$$command_json" >> compile_commands.json ; \
done
@for file in $(SRC_CX); do \
compile_cmd="$(CX) $(CX_FLAG) $(HEAD_PATH) -c $${file} -o $${file}.$(CX_MARK).o" \
command_json=" { \"arguments\": [ \"$$compile_cmd\" ], \"directory\": \"${PWD}\", \"file\": \"$$file\" },"; \
echo -e "$$command_json" >> compile_commands.json ; \
done
@sed -i '$$ s/,$$/\n]/' compile_commands.json
# 定义清理规则
clean:
rm -fR $(TARGET) $(BUILD_DIR)
.PHONY: all clean json
然后是一个windows下,使用minGW编译简单可执行程序的Makefile
# linux下使用/bin/dash,echo -e 会将 -e 输出,windows下可删除此行
# SHELL = /bin/bash
# 定义目标文件名称
BUILD_DIR := ./build/
TARGET := ./execute.exe
# 定义工具链路径和编译器路径
TOOLCHAIN_DIR := C:/PATH/w64devkit/
COMPILER_PATH := $(TOOLCHAIN_DIR)bin/
# 定义编译器和链接器命令和标志
CC := $(COMPILER_PATH)gcc.exe
CX := $(COMPILER_PATH)g++.exe
LD := $(COMPILER_PATH)g++.exe
SZ := $(COMPILER_PATH)size.exe
OBJCOPY := $(COMPILER_PATH)objcopy.exe
CC_MARK := .xc
CX_MARK := .xc++
CC_FLAG := -xc
CX_FLAG := -xc++
LD_FLAG :=
# 定义头文件、库文件和源文件路径和类型
HEAD_PATH := -I$(TOOLCHAIN_DIR)x86_64-w64-mingw32/include/
LIB_PATH := -L$(TOOLCHAIN_DIR)x86_64-w64-mingw32/lib/
LIB_FLAG :=
SRC_LIB :=
SRC_CC :=
SRC_CX :=
EXCLUDE_FILES :=
HEAD_TYPE := .h .hpp .hh
LIB_TYPE := .a .so
CC_TYPE := .c
CX_TYPE := .cpp
# 设置源文件列表
HEAD_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(HEAD_TYPE)))
LIB_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(LIB_TYPE)))
CC_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(CC_TYPE)))
CX_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(CX_TYPE)))
LOCAL_HEAD := $(shell find . -type f -regex ".*\.\($(HEAD_TYPE_SIFT)\)" -printf "%P ")
LOCAL_LIB := $(shell find . -type f -regex ".*\.\($(LIB_TYPE_SIFT)\)" -printf "%P ")
LOCAL_CC := $(shell find . -type f -regex ".*\.\($(CC_TYPE_SIFT)\)" -printf "%P ")
LOCAL_CX := $(shell find . -type f -regex ".*\.\($(CX_TYPE_SIFT)\)" -printf "%P ")
HEAD_PATH += $(addprefix -I,$(sort $(dir $(filter-out $(EXCLUDE_FILES),$(LOCAL_HEAD)))))
SRC_LIB += $(filter-out $(EXCLUDE_FILES),$(LOCAL_LIB))
SRC_CC += $(filter-out $(EXCLUDE_FILES),$(LOCAL_CC))
SRC_CX += $(filter-out $(EXCLUDE_FILES),$(LOCAL_CX))
# 生成对象文件和依赖文件的列表
OBJ_CC := $(addprefix $(BUILD_DIR),$(notdir $(SRC_CC:%=%$(CC_MARK).o)))
OBJ_CX := $(addprefix $(BUILD_DIR),$(notdir $(SRC_CX:%=%$(CX_MARK).o)))
# 生成源文件查找路径
vpath % $(sort $(dir $(SRC_CC))) $(sort $(dir $(SRC_CX)))
# 定义all依赖
all : $(TARGET)
@echo " CHECK $<"
@$(SZ) $<
# 定义编译和链接命令和规则
$(TARGET): $(OBJ_CC) $(OBJ_CX) $(SRC_LIB)
@echo " LN $^ -> $@"
@$(LD) $(LD_FLAG) -o $@ $^ $(LIB_PATH) $(LIB_FLAG) $(SRC_LIB)
$(BUILD_DIR)%$(CC_MARK).o : % Makefile | $(BUILD_DIR)
@echo " CC $<"
@$(CC) $(CC_FLAG) $(HEAD_PATH) -MMD -MP -MF"$(@:%.o=%.d)" -c $< -o $@
$(BUILD_DIR)%$(CX_MARK).o : % Makefile | $(BUILD_DIR)
@echo " CX $<"
@$(CX) $(CX_FLAG) $(HEAD_PATH) -MMD -MP -MF"$(@:%.o=%.d)" -c $< -o $@
$(BUILD_DIR) :
@echo " MK $@"
@mkdir $@
# 包含依赖文件
-include $(wildcard $(BUILD_DIR)*.d)
# 生成compile_commands.json文件的规则
json: $(SRC_CC) $(SRC_CX) Makefile
@echo "Generating compile_commands.json"
@rm -f compile_commands.json
@echo "[" > compile_commands.json
@for file in $(SRC_CC); do \
compile_cmd="$(CC) $(CC_FLAG) $(HEAD_PATH) -c $${file} -o $${file}.$(CC_MARK).o" \
command_json=" { \"arguments\": [ \"$$compile_cmd\" ], \"directory\": \"${PWD}\", \"file\": \"$$file\" },"; \
echo -e "$$command_json" >> compile_commands.json ; \
done
@for file in $(SRC_CX); do \
compile_cmd="$(CX) $(CX_FLAG) $(HEAD_PATH) -c $${file} -o $${file}.$(CX_MARK).o" \
command_json=" { \"arguments\": [ \"$$compile_cmd\" ], \"directory\": \"${PWD}\", \"file\": \"$$file\" },"; \
echo -e "$$command_json" >> compile_commands.json ; \
done
@sed -i '$$ s/,$$/\n]/' compile_commands.json
# 定义清理规则
clean:
rm -fR $(TARGET) $(BUILD_DIR)
.PHONY: all clean json
最后是一个windows下,使用arm-none-eabi-gcc为 STM32F103C8T6 编译程序的Makefile,可以同事编译.c和.s
# linux下使用/bin/dash,echo -e 会将 -e 输出,windows下可删除此行
# SHELL = /bin/bash
# 定义目标文件名称
BUILD_DIR := ./build/
TARGET := app
# 定义工具链路径和编译器路径
TOOLCHAIN_DIR := C:/PATH/Arm_Development_Toolchains/gcc-arm-none-eabi-10.3-2021.10/
COMPILER_PATH := $(TOOLCHAIN_DIR)bin/
# 定义编译器和链接器命令和标志
CC := $(COMPILER_PATH)arm-none-eabi-gcc.exe
CX := $(COMPILER_PATH)arm-none-eabi-gcc.exe
LD := $(COMPILER_PATH)arm-none-eabi-gcc.exe
SZ := $(COMPILER_PATH)arm-none-eabi-size.exe
OBJCOPY := $(COMPILER_PATH)arm-none-eabi-objcopy.exe
CC_MARK := .xc
CX_MARK := .xs
CC_FLAG := -xc -mcpu=cortex-m3 -mthumb -DUSE_HAL_DRIVER -DSTM32F103xB -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2
CX_FLAG := -x assembler-with-cpp -mcpu=cortex-m3 -mthumb -DUSE_HAL_DRIVER -DSTM32F103xB -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2
LD_FLAG := -mcpu=cortex-m3 -mthumb -specs=nano.specs -TConfig/STM32F103C8Tx_FLASH.ld -Wl,--gc-sections
# 定义头文件、库文件和源文件路径和类型
HEAD_PATH :=
LIB_PATH :=
LIB_FLAG := -lc -lm -lnosys
SRC_LIB :=
SRC_CC :=
SRC_CX :=
EXCLUDE_FILES :=
HEAD_TYPE := .h
LIB_TYPE := .a .so
CC_TYPE := .c
CX_TYPE := .s
# 设置源文件列表
HEAD_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(HEAD_TYPE)))
LIB_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(LIB_TYPE)))
CC_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(CC_TYPE)))
CX_TYPE_SIFT := $(patsubst .%,%,$(subst $(empty) .,\|,$(CX_TYPE)))
LOCAL_HEAD := $(shell find . -type f -regex ".*\.\($(HEAD_TYPE_SIFT)\)" -printf "%P ")
LOCAL_LIB := $(shell find . -type f -regex ".*\.\($(LIB_TYPE_SIFT)\)" -printf "%P ")
LOCAL_CC := $(shell find . -type f -regex ".*\.\($(CC_TYPE_SIFT)\)" -printf "%P ")
LOCAL_CX := $(shell find . -type f -regex ".*\.\($(CX_TYPE_SIFT)\)" -printf "%P ")
HEAD_PATH += $(addprefix -I,$(sort $(dir $(filter-out $(EXCLUDE_FILES),$(LOCAL_HEAD)))))
SRC_LIB += $(filter-out $(EXCLUDE_FILES),$(LOCAL_LIB))
SRC_CC += $(filter-out $(EXCLUDE_FILES),$(LOCAL_CC))
SRC_CX += $(filter-out $(EXCLUDE_FILES),$(LOCAL_CX))
# 生成对象文件和依赖文件的列表
OBJ_CC := $(addprefix $(BUILD_DIR),$(notdir $(SRC_CC:%=%$(CC_MARK).o)))
OBJ_CX := $(addprefix $(BUILD_DIR),$(notdir $(SRC_CX:%=%$(CX_MARK).o)))
# 生成源文件查找路径
vpath % $(sort $(dir $(SRC_CC))) $(sort $(dir $(SRC_CX)))
# 定义all依赖
all : $(TARGET).elf $(TARGET).hex $(TARGET).bin
@echo " CHECK $<"
@$(SZ) $<
$(TARGET).hex : $(TARGET).elf
@echo " OBJCOPY $^ -> $@"
@$(OBJCOPY) -O ihex $< $@
$(TARGET).bin : $(TARGET).elf
@echo " OBJCOPY $^ -> $@"
@$(OBJCOPY) -O binary -S $< $@
# 定义编译和链接命令和规则
$(TARGET).elf : $(OBJ_CC) $(OBJ_CX) $(SRC_LIB)
@echo " LN $^ -> $@"
@$(LD) $(LD_FLAG) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections -o $@ $^ $(LIB_PATH) $(LIB_FLAG) $(SRC_LIB)
$(BUILD_DIR)%$(CC_MARK).o : % Makefile | $(BUILD_DIR)
@echo " CC $<"
@$(CC) $(CC_FLAG) $(HEAD_PATH) -Wa,-a,-ad,-alms="$(@:%.o=%.lst)" -MMD -MP -MF"$(@:%.o=%.d)" -c $< -o $@
$(BUILD_DIR)%$(CX_MARK).o : % Makefile | $(BUILD_DIR)
@echo " CX $<"
@$(CX) $(CX_FLAG) $(HEAD_PATH) -Wa,-a,-ad,-alms="$(@:%.o=%.lst)" -MMD -MP -MF"$(@:%.o=%.d)" -c $< -o $@
$(BUILD_DIR) :
@echo " MK $@"
@mkdir $@
# 包含依赖文件
-include $(wildcard $(BUILD_DIR)*.d)
# 生成compile_commands.json文件的规则
json: $(SRC_CC) $(SRC_CX) Makefile
@echo "Generating compile_commands.json"
@rm -f compile_commands.json
@echo "[" > compile_commands.json
@for file in $(SRC_CC); do \
compile_cmd="$(CC) $(CC_FLAG) $(HEAD_PATH) -c $${file} -o $${file}.$(CC_MARK).o" \
command_json=" { \"arguments\": [ \"$$compile_cmd\" ], \"directory\": \"${PWD}\", \"file\": \"$$file\" },"; \
echo -e "$$command_json" >> compile_commands.json ; \
done
@for file in $(SRC_CX); do \
compile_cmd="$(CX) $(CX_FLAG) $(HEAD_PATH) -c $${file} -o $${file}.$(CX_MARK).o" \
command_json=" { \"arguments\": [ \"$$compile_cmd\" ], \"directory\": \"${PWD}\", \"file\": \"$$file\" },"; \
echo -e "$$command_json" >> compile_commands.json ; \
done
@sed -i '$$ s/,$$/\n]/' compile_commands.json
# 定义清理规则
clean:
rm -fR $(BUILD_DIR) $(TARGET).elf $(TARGET).hex $(TARGET).bin
.PHONY: all clean json
通过对比,大家可以发现,对于这个Makefile,无论是编译linux还是windows,opencv还是单片机,主体都是不需要修改的,只需要改下编译器和标志
最后说三个注意地方:
一、是这个Makefile会自动搜索所在目录及子目录下的所有符合HEAD_TYPE、LIB_TYPE、CC_TYPE 、CX_TYPE 指定后缀的文件,所以使用这个Makefile时,要保证所在目录及子目录下不要包含无用代码,实在要包含,就在EXCLUDE_FILES 中指定,会被排除
二、是可以使用make json命令,生成compile_commands.json,使用clangd调试代码很方便
三、如果要编译动态库,比如.so,需要先把TARGET的后缀换成.so, 然后在LD_FLAG 后添加 -shared
如果要编译静态库,比如.a, 先把TARGET的后缀换成.a,例如lib.a
新增
AR := $(COMPILER_PATH)ar
最后把
@$(LD) $(LD_FLAG) -o $@ $^ $(LIB_PATH) $(LIB_FLAG) $(SRC_LIB)
替换为
@$(AR) $(AR_FLAG) rcs $@ $^