C/C++通用Makefile

        最近的项目又回到了Linux上运行,这就需要在Linux下编译项目,写Makefile针对习惯了Windows的程序员来说是一件痛苦的事,如果有一个通用的Makefile该多好啊,本着这样的目的,我再次研究了一下Makefile,写出了一个实用的通用Makefile,该Makefile在Windows以及Linux平台下作了一些简单测试,未发现问题,如果大家在使用过程中发现有问题可以联系我。话不多说,直接上代码:

###############################################################################
#
#  C/C++通用Makefile
#
#  作者:Witton Bell         E_Mail:[email protected]
#
#  版本V1.0 (2017/02/23)
#		初始版本,可以生成App,Share以及Static库等等
#  版本V1.1 (2017/02/28)
#		添加预预编译头的支持,将生成的中间文件放在debug或者release目录下
#  版本V1.2 (2017/03/12)
#		优化以及fixed:修改头文件后,不会编译
#  版本V1.3 (2017/03/13)
#		优化:将依赖文件放在.deps目录下,make clean时不会重复生成
#
#  一个项目只需要在顶层目录配置一个Makefile,不需要各个目录单独配置,简单方便实用
#  原文链接:http://blog.csdn.net/witton/article/details/56670748
#
#  功能说明:
#  1.支持Windows以及Linux下的可执行文件(App)、动态库(Share)以及静态库(Static)的生成
#  2.支持生成Map文件,Bin文件以及十六进制文件
#  3.支持C/C++的预编译头,以加快编译速度,包括同时存在C与C++预编译头的情况
#  4.生成的中间以及目标代码会指定存放在debug或者release目录下
#  5.支持生成进度显示
#
###############################################################################

# 可以修改的区域

# 目标文件的名字(必须指定)
TARGETNAME :=

#目标类型可以是APP, STATIC或者SHARED
TARGETTYPE := APP

#配置类型可以是Debug或者Release
CONFIG ?= Debug

# C 预编译头文件,支持带路径
PCH_H =
# C++ 预编译头文件,支持带路径
PCH_X_H =

#源文件目录列表,可以填多个目录,如:src1 src2 ../src
#如果没填写则默认为当前目录
SRC_ROOT_DIRS := .

#排除的目录列表,需要带上相对路径
EXCLUDE_DIRS :=

#排除的文件列表,需要带上相对路径
EXCLUDE_FILES :=

#头文件所在目录列表
INCLUDE_DIRS :=
#库文件所在目录列表
LIBRARY_DIRS :=
#公共库文件名列表
LIBRARY_NAMES := 
#公共宏定义
PREPROCESSOR_MACROS :=

#各种编译链接参数
#汇编编译参数
ASFLAGS := -f win64
#ld链接参数
LDFLAGS := -Wl,-gc-sections
#公共编译参数,根据情况作修改
COMMONFLAGS := -g -ffunction-sections -finput-charset=UTF-8 -fexec-charset=UTF-8 -fwide-exec-charset=UTF-16LE
#额外的库文件
EXTERNAL_LIBS := 

#工具集,Windows下最好是填写MinGW命令行工具的绝对路径,并且路径中需要使用/分隔
#因为Windows下的一些命令无相应的参数,会报错,
#比如mkdir, cp, rm, echo等等,如果安装了Git及命令行工具,建议直接使用git安装目录下usr/bin路径
CC := gcc
CXX := g++
LD := $(CXX)
ASM := nasm
AR := ar
OBJCOPY := objcopy
CP := cp
MKDIR = mkdir
RM = rm -rf
ECHO = echo
SHELL = /bin/sh

#根据配置类型填写不同的参数
ifeq ($(CONFIG), Debug)
	PREPROCESSOR_MACROS += DEBUG
	LIBRARY_NAMES += 
	ADDITIONAL_LINKER_INPUTS := 
	MACOS_FRAMEWORKS := 
	LINUX_PACKAGES := 
	CFLAGS := -O0 -Wall
	CXXFLAGS := -O0
else
	PREPROCESSOR_MACROS +=
	LIBRARY_NAMES += 
	ADDITIONAL_LINKER_INPUTS := 
	MACOS_FRAMEWORKS := 
	LINUX_PACKAGES := 
	CFLAGS := -O3
	CXXFLAGS := -O3
endif

#需要编译的源文件的扩展名列表
SRC_EXTS := .c .cpp .cc .cxx .c++ .s .S .asm

#如果是在Linux下编译,需要打开此开关
#IS_LINUX_PROJECT := 1

#如果需要生成Map文件,需要打开此开关
#GENERATE_MAP_FILE := 1

#如果生成Bin文件,需要打开此开关
#GENERATE_BIN_FILE := 1

#如果生成十六进制文件,需要打开此开关
#GENERATE_IHEX_FILE := 1

#是否显示详细的命令行
#SHOW_CMD_DETAIL := 1

#可修改区域结束,以下区域不建议修改,除非特别了解其含义

#======================华丽的分割线=============================

#小写函数
to_lowercase = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))

BINARYDIR := $(call to_lowercase,$(CONFIG))

ifeq ($(BINARYDIR),)
error:
	$(error Invalid configuration, please check your inputs)
endif

DEPS_DIR := .deps

#如果没配置源文件目录,则默认为当前目录
ifeq ($(SRC_ROOT_DIRS),)
	SRC_ROOT_DIRS = .
endif

define walk
$(wildcard $(addprefix $(1)/*, $(SRC_EXTS))) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef

#匹配所有源文件
SOURCEFILES := $(foreach e, $(SRC_ROOT_DIRS), $(call walk, $(e)))
#匹配所有需要排除的源文件
EXCLUDE_SOURCE := $(foreach e, $(EXCLUDE_DIRS), $(call walk, $(e)))
EXCLUDE_FILES += $(EXCLUDE_SOURCE)
EXTERNAL_LIBS_COPIED := $(foreach lib, $(EXTERNAL_LIBS),$(BINARYDIR)/$(notdir $(lib)))

#设置C预编译相关变量
ifneq ($(PCH_H),)
PCH = $(PCH_H).gch
PCH_FLAGS := -Winvalid-pch -include $(BINARYDIR)/$(PCH_H)
PCH_FILE := $(BINARYDIR)/$(PCH)
endif

#设置C++预编译相关变量
ifneq ($(PCH_X_H),)
PCH_X = $(PCH_X_H).gch
PCH_X_FLAGS := -Winvalid-pch -include $(BINARYDIR)/$(PCH_X_H)
PCH_X_FILE := $(BINARYDIR)/$(PCH_X)
endif

START_GROUP := -Wl,--start-group
END_GROUP := -Wl,--end-group
INCLUDE_DIRS += .

#处理头文件目录
CFLAGS += $(addprefix -I,$(INCLUDE_DIRS))
CXXFLAGS += $(addprefix -I,$(INCLUDE_DIRS))

#处理宏定义
CFLAGS += $(addprefix -D,$(PREPROCESSOR_MACROS))
CXXFLAGS += $(addprefix -D,$(PREPROCESSOR_MACROS))
ASFLAGS += $(addprefix -D,$(PREPROCESSOR_MACROS))

#处理框架
CFLAGS += $(addprefix -framework ,$(MACOS_FRAMEWORKS))
CXXFLAGS += $(addprefix -framework ,$(MACOS_FRAMEWORKS))
LDFLAGS += $(addprefix -framework ,$(MACOS_FRAMEWORKS))

#处理库目录
LDFLAGS += $(addprefix -L,$(LIBRARY_DIRS))

CFLAGS += $(COMMONFLAGS)
CXXFLAGS += $(COMMONFLAGS)
LDFLAGS += $(COMMONFLAGS)

ifeq ($(GENERATE_MAP_FILE),1)
LDFLAGS += -Wl,-Map=$(BINARYDIR)/$(basename $(TARGETNAME)).map
endif

LIBRARY_LDFLAGS = $(addprefix -l,$(LIBRARY_NAMES))

ifeq ($(IS_LINUX_PROJECT),1)

ifeq ($(TARGETTYPE),SHARED)
TempName = $(addsuffix .so,$(basename $(TARGETNAME)))
TARGETNAME := $(TempName)
endif

	RPATH_PREFIX := -Wl,--rpath='$$ORIGIN/../
	LIBRARY_LDFLAGS += $(EXTERNAL_LIBS)
	LIBRARY_LDFLAGS += -Wl,--rpath='$$ORIGIN'
	LIBRARY_LDFLAGS += $(addsuffix ',$(addprefix $(RPATH_PREFIX),$(dir $(EXTERNAL_LIBS))))
	
	#如果是Linux下的共享库(Share)项目,则需要添加-fPIC参数,以实现位置无关代码
	ifeq ($(TARGETTYPE),SHARED)
		CFLAGS += -fPIC
		CXXFLAGS += -fPIC
		ASFLAGS += -fPIC
		LIBRARY_LDFLAGS += -Wl,-soname,$(TARGETNAME)
	endif
	
	ifneq ($(LINUX_PACKAGES),)
		PACKAGE_CFLAGS := $(foreach pkg,$(LINUX_PACKAGES),$(shell pkg-config --cflags $(pkg)))
		PACKAGE_LDFLAGS := $(foreach pkg,$(LINUX_PACKAGES),$(shell pkg-config --libs $(pkg)))
		CFLAGS += $(PACKAGE_CFLAGS)
		CXXFLAGS += $(PACKAGE_CFLAGS)
		LIBRARY_LDFLAGS += $(PACKAGE_LDFLAGS)
	endif
else
	LIBRARY_LDFLAGS += $(EXTERNAL_LIBS)

ifeq ($(TARGETTYPE),APP)
TempName = $(addsuffix .exe,$(basename $(TARGETNAME)))
TARGETNAME := $(TempName)
endif

ifeq ($(TARGETTYPE),SHARED)
TempName = $(addsuffix .dll,$(basename $(TARGETNAME)))
TARGETNAME := $(TempName)
endif

endif

LIBRARY_LDFLAGS += $(ADDITIONAL_LINKER_INPUTS)

#静态库都是以.a为后缀
ifeq ($(TARGETTYPE),STATIC)
TempName = $(addsuffix .a,$(basename $(TARGETNAME)))
TARGETNAME := $(TempName)
endif

ifeq ($(STARTUPFILES),)
	all_source_files := $(SOURCEFILES)
else
	all_source_files := $(STARTUPFILES) $(filter-out $(STARTUPFILES),$(SOURCEFILES))
endif

define HandlePath
$(foreach x,$(foreach x,$(foreach x,$(subst //,/,$(1)),$(subst ../,*/,$(x))),$(subst ./,,$(x))),$(subst */,../,$(x)))
endef

AllSource := $(call HandlePath, $(all_source_files))
AllExcludeSource := $(call HandlePath, $(EXCLUDE_FILES))

Source := $(filter-out $(AllExcludeSource),$(AllSource))

CompileObjs := $(foreach x,$(SRC_EXTS),$(patsubst %$(x),%.o,$(filter %$(x),$(Source))))
all_objs := $(foreach x,$(CompileObjs),$(BINARYDIR)/$(x))

ASM_EXT := .s .S .asm
CompileCObjs := $(foreach x,$(filter-out $(ASM_EXT),$(SRC_EXTS)),$(patsubst %$(x),%.d,$(filter %$(x),$(Source))))
all_Cobjs := $(foreach x,$(CompileCObjs),$(BINARYDIR)/$(x))
all_Deps := $(subst ../,__/, $(foreach x,$(CompileCObjs),$(DEPS_DIR)/$(x)))

DEPS := $(all_Deps:.o=.d)

revert0 = $(2) $(1)
define revert
$(foreach x,$(DEPS),$(eval TempStep = $(call revert0, $(TempStep),$(x)))) $(TempStep)
endef

AllStep := $(call revert,$(DEPS))
AllStep += $(CompileObjs)

WordNum := $(words $(AllStep))

ProgressInfo := $(foreach x,$(AllStep),$(eval Counter += A)$(addsuffix .$(words $(Counter)), $(basename $(x))))

define FindProgress
$(foreach x,$(ProgressInfo),$(if $(filter $(basename $(x)),$(1)),$(subst .,,$(suffix $(x))),))
endef

define ShowProgress
$(strip $(call FindProgress,$(basename $(1))))/$(WordNum)
endef

IS_GCC_ASM :=
ifneq ($(filter $(ASM),$(CXX)),)
IS_GCC_ASM = 1
else
ifneq ($(filter $(ASM),$(CC)),)
IS_GCC_ASM = 1
endif
endif

ifeq ($(IS_GCC_ASM),1)
CompileA 	:= $(ASM) $(CFLAGS) $(ASFLAGS) -c
else
CompileA 	:= $(ASM) $(ASFLAGS)
endif

CompileC 	:= $(CC) $(PCH_FLAGS) $(CFLAGS) -c
CompileCXX	:= $(CXX) $(PCH_X_FLAGS) $(CXXFLAGS) -c

ifneq ($(SHOW_CMD_DETAIL),)
define CompileSrc
	@$(MKDIR) -p $(BINARYDIR)/$(subst ../,__/, $(dir $(1)))
	@$(ECHO) -n [$(call ShowProgress,$(2))]
	$(3) $(2) -o $(subst ../,__/, $(1))
endef
else
define CompileSrc
	@$(MKDIR) -p $(BINARYDIR)/$(subst ../,__/, $(dir $(1)))
	@$(ECHO) [$(call ShowProgress,$(2))] Compile $(2)
	@$(3) $(2) -o $(subst ../,__/, $(1))
endef
endif

define CompileCDep
	@$(MKDIR) -p $(subst ../,__/, $(dir $(1)))
	@$(ECHO) [$(call ShowProgress,$(DEPS_DIR)/$(2))] Generate $(subst ../,__/,$(1))
	@$(CC) -MM $(CFLAGS) $(2) > $(DEPS_DIR)/temp
	@$(ECHO) -n $(subst ./,, $(dir $(1))) > $(subst ../,__/,$(1))
	@$(CC) -MM $(CFLAGS) $(2) >> $(subst ../,__/,$(1))
endef

define CompileCXXDep
	@$(MKDIR) -p $(subst ../,__/, $(dir $(1)))
	@$(ECHO) [$(call ShowProgress,$(DEPS_DIR)/$(2))] Generate $(subst ../,__/,$(1))
	@$(CXX) -MM $(CXXFLAGS) $(2) > $(DEPS_DIR)/temp
	@$(ECHO) -n $(subst ./,, $(dir $(1))) > $(subst ../,__/,$(1))
	@$(CXX) -MM $(CXXFLAGS) $(2) >> $(subst ../,__/,$(1))
endef

PRIMARY_OUTPUTS :=

ifeq ($(GENERATE_BIN_FILE),1)
PRIMARY_OUTPUTS += $(BINARYDIR)/$(basename $(TARGETNAME)).bin
endif

ifeq ($(GENERATE_IHEX_FILE),1)
PRIMARY_OUTPUTS += $(BINARYDIR)/$(basename $(TARGETNAME)).ihex
endif

ifeq ($(PRIMARY_OUTPUTS),)
PRIMARY_OUTPUTS := $(BINARYDIR)/$(TARGETNAME)
endif

.PHONY: all clean rebuild distclean cleandeps help

.SUFFIXES:

all: $(PRIMARY_OUTPUTS)
	@$(RM) $(DEPS_DIR)/temp
	@$(ECHO) Built OK.

clean:
	@$(RM) $(BINARYDIR)

cleandeps:
	@$(RM) $(DEPS_DIR)

distclean: clean cleandeps
	
rebuild: clean all

$(BINARYDIR)/$(basename $(TARGETNAME)).bin: $(BINARYDIR)/$(TARGETNAME)
	@$(OBJCOPY) -O binary $< $(subst ../,__/, $@)

$(BINARYDIR)/$(basename $(TARGETNAME)).ihex: $(BINARYDIR)/$(TARGETNAME)
	@$(OBJCOPY) -O ihex $< $(subst ../,__/, $@) 

ifeq ($(TARGETTYPE),APP)
ifneq ($(SHOW_CMD_DETAIL),)
$(BINARYDIR)/$(TARGETNAME): $(all_objs) $(EXTERNAL_LIBS)
	$(LD) -o $(subst ../,__/, $@) $(LDFLAGS) $(START_GROUP) $(subst ../,__/, $(all_objs)) $(LIBRARY_LDFLAGS) $(END_GROUP)
else
$(BINARYDIR)/$(TARGETNAME): $(all_objs) $(EXTERNAL_LIBS)
	@$(ECHO) Link App $(subst ../,__/, $@)
	@$(LD) -o $(subst ../,__/, $@) $(LDFLAGS) $(START_GROUP) $(subst ../,__/, $(all_objs)) $(LIBRARY_LDFLAGS) $(END_GROUP)
endif
endif

ifeq ($(TARGETTYPE),SHARED)
ifneq ($(SHOW_CMD_DETAIL),)
$(BINARYDIR)/$(TARGETNAME): $(all_objs) $(EXTERNAL_LIBS)
	$(LD) -shared -o $(subst ../,__/, $@) $(LDFLAGS) $(START_GROUP) $(subst ../,__/, $(all_objs)) $(LIBRARY_LDFLAGS) $(END_GROUP)
else
$(BINARYDIR)/$(TARGETNAME): $(all_objs) $(EXTERNAL_LIBS)
	@$(ECHO) Link Share lib $(subst ../,__/, $@)
	@$(LD) -shared -o $(subst ../,__/, $@) $(LDFLAGS) $(START_GROUP) $(subst ../,__/, $(all_objs)) $(LIBRARY_LDFLAGS) $(END_GROUP)
endif
endif

ifeq ($(TARGETTYPE),STATIC)
ifneq ($(SHOW_CMD_DETAIL),)
$(BINARYDIR)/$(TARGETNAME): $(all_objs)
	$(AR) -r $(subst ../,__/, $@) $(subst ../,__/, $^)
else
$(BINARYDIR)/$(TARGETNAME): $(all_objs)
	@$(ECHO) Link Static lib $(subst ../,__/, $@)
	@$(AR) -r $(subst ../,__/, $@) $(subst ../,__/, $^)
endif
endif

$(BINARYDIR):
	@$(MKDIR) $(BINARYDIR)

#Makefile的生成规则
#生成依赖文件
$(DEPS_DIR)/%.d : %.c
	$(call CompileCDep,$@,$<)

$(DEPS_DIR)/%.d : %.cpp
	$(call CompileCXXDep,$@,$<)

$(DEPS_DIR)/%.d : %.cxx
	$(call CompileCXXDep,$@,$<)

$(DEPS_DIR)/%.d : %.cc
	$(call CompileCXXDep,$@,$<)

$(DEPS_DIR)/%.d : %.c++
	$(call CompileCXXDep,$@,$<)

#C文件的生成规则
$(BINARYDIR)/%.o : %.c $(PCH_FILE)
	$(call CompileSrc,$@,$<,$(CompileC))

#C++的.cpp文件的生成规则
$(BINARYDIR)/%.o : %.cpp $(PCH_X_FILE)
	$(call CompileSrc,$@,$<,$(CompileCXX))

#C++的.cc文件的生成规则
$(BINARYDIR)/%.o : %.cc $(PCH_X_FILE)
	$(call CompileSrc,$@,$<,$(CompileCXX))

#C++的.cxx文件的生成规则
$(BINARYDIR)/%.o : %.cxx $(PCH_X_FILE)
	$(call CompileSrc,$@,$<,$(CompileCXX))

#C++的.c++文件的生成规则
$(BINARYDIR)/%.o : %.c++ $(PCH_X_FILE)
	$(call CompileSrc,$@,$<,$(CompileCXX))

#Asm的.S文件生成规则
$(BINARYDIR)/%.o : %.S
	$(call CompileSrc,$@,$<,$(CompileA))

#Asm的.s文件生成规则
$(BINARYDIR)/%.o : %.s
	$(call CompileSrc,$@,$<,$(CompileA))

#Asm的.asm文件生成规则
$(BINARYDIR)/%.o : %.asm
	$(call CompileSrc,$@,$<,$(CompileA))

ifneq ($(PCH_H),)
#C预编译头文件的生成规则 
$(BINARYDIR)/$(PCH) : $(PCH_H)
	@$(MKDIR) -p $(subst ../,__/, $(dir $@))
	@$(ECHO) Precompiled C header $<
	@$(CC) $(CFLAGS) $> $^ -o $(subst ../,__/, $@)
	@$(CP) $(PCH_H) $(BINARYDIR)/$(PCH_H)
endif

ifneq ($(PCH_X_H),)
#C++预编译头文件的生成规则 
$(BINARYDIR)/$(PCH_X) : $(PCH_X_H)
	@$(MKDIR) -p $(subst ../,__/, $(dir $@))
	@$(ECHO) Precompiled C++ header $<
	@$(CXX) $(CXXFLAGS) $> $^ -o $(subst ../,__/, $@)
	@$(CP) $(PCH_X_H) $(BINARYDIR)/$(PCH_X_H)
endif

ifndef NODEP  
ifneq ($(DEPS),)  
sinclude $(DEPS)  
endif  
endif

#生成规则结束

help:
	@$(ECHO) "C/C++ general Makefile V1.3"
	@$(ECHO) "Copyright(C) 2017     E_Mail:[email protected] by Witton Bell"
	@$(ECHO) "Usage:make [Target]"
	@$(ECHO) "Target:"
	@$(ECHO) "     all          compile and link to target directory(debug or release)"
	@$(ECHO) "     clean        remove target directory(debug or release)"
	@$(ECHO) "     cleandeps    remove depends directory(.deps)"
	@$(ECHO) "     distclean    run target clean and cleandeps"
	@$(ECHO) "     rebuild      run target clean and all"
	@$(ECHO) "     help         show this help"



最终的运行结果如下图所示:

祝玩得开心!

你可能感兴趣的:(跨平台,#,C/C++,Linux,makefile,通用makefile,C++,makefile,预编译makefile,linux)