A Generic Makefile for C/C++ Program

2018年08月02日

I used to work a lot on scientific computing with C/C++, in which compiling the source code could be quite inconvenient. After some iteration, I came up with following Makefile. Just drop it into a source code directory and make.

Following is a simple adjustable Makefile which should locate inside your code directory.

##==========================================================================
## 需要调整的项
##==========================================================================
# 可执行文件名称
APP =

# 编译模式: Debug, Release 
MODE = Release
#MODE = Debug

# 使用GPU或OMP
#USEOMP=true
USEOMP=false

USEGPU=true
#USEGPU=false

#NORAND=true
NORAND=false

# 宏定义
MACRO_DEFINE= -D _ElemType_=double

# 源文件目录
SRC_DIRS = .

# 需要指明同步的文件后缀
FILETYPE = Makefile .R .r .sh
# 同步选项
upload: SYNC_OPTIONS_UPLOAD= --exclude=*/.hg/
download: SYNC_OPTIONS_DOWNLOAD= --exclude=*/.hg/ --include=*.dat 

-include $(shell echo $$HOME)/.local/include/Makefile.in

Put following generic Makefile in to a location like $HOME/.local/include/.

# Generic Makefile for C/C++ Program

###==========================================================================
### 需要调整的项
###==========================================================================
## 可执行文件名称
#APP =

## 需要指明同步的文件后缀
#FILETYPE = Makefile .R .r .sh

## 编译模式: Debug, Release 
#MODE = Release
##MODE = Debug

## 使用GPU或OMP
##USEOMP=true
#USEOMP=false

#USEGPU=true
##USEGPU=false

##NORAND=true
#NORAND=false

## 宏定义
#MACRO_DEFINE= -D _ElemType_=double

## 源文件目录
#SRC_DIRS = .

## 同步选项
#upload: SYNC_OPTIONS_UPLOAD= --exclude=*/.hg/
#download: SYNC_OPTIONS_DOWNLOAD= --exclude=*/.hg/ --include=*.dat 

#-include $(shell echo $$HOME)/.local/include/Makefile
##==========================================================================
## 不常修改的项
##==========================================================================
# Rsync 同步选项
SYNC_OPTIONS+= -amz -v -b -h --progress --backup-dir=backup.bak -Lpt -u --partial
SSH_HOST_ALL=$(shell awk -F: '/^SSH_HOST_ALL= /{$$1="";$$NF="";print $$0}' $$HOME/.myrc)
SSH_HOST=$(shell awk '/^SSH_HOST= /{print $$2}' $$HOME/.myrc)
SSH_HOST_DIR=$(shell echo $(SSH_HOST_ALL) | \
	     awk 'BEGIN{RS=" "} \
	         /$(SSH_HOST)/{\
		    match($$0,"{.*}");\
		    print substr($$0,RSTART+1,RLENGTH-2)}')

# 预处理选项
CPPFLAGS += 

# CUDA 选项 计算能力
GENCODE_SM10    = -gencode arch=compute_10,code=sm_10
GENCODE_SM13    = -gencode arch=compute_13,code=sm_13
GENCODE_SM20    = -gencode arch=compute_20,code=sm_20
GENCODE_SM30    = -gencode arch=compute_30,code=sm_30 -gencode arch=compute_35,code=sm_35
GENCODE_FLAGS   = $(GENCODE_SM20) 

# OS-specific build flags
OS_SIZE = $(shell uname -m | sed -e "s/i.86/32/" -e "s/x86_64/64/")

# CUDA位置
CUDA_PATH       ?= /usr/local/cuda-5.5
ifeq ($(OS_SIZE),32)
    CUDA_LIB_PATH   ?= $(CUDA_PATH)/lib
else
    CUDA_LIB_PATH   ?= $(CUDA_PATH)/lib64
endif

# 需要额外添加的库
LIBS +=
CUDA_LIBS   += -I$(CUDA_PATH)/include -I. -I$(CUDA_PATH)/samples/0_Simple -I$(CUDA_PATH)/samples/common/inc

# 需要指明的依赖
DEPS_MORE += $(firstword $(MAKEFILE_LIST))

# 链接选项
LDFLAGS += -lncurses -lrt
CUDA_LDFLAGS   += -L$(CUDA_LIB_PATH) -lcudart -lcurand

# 源文件后缀名
SRCEXTS += .c .C .cc .cpp .CPP .c++ .cxx .cp
# 头文件后缀名
HDREXTS += .h .H .hh .hpp .HPP .h++ .hxx .hp
ifeq ($(USEGPU),true) # CUDA program
SRCEXTS += .cu
HDREXTS += .cuh
endif

# 编译选项: -pg 生成性能测试报表; -coverage 生成代码覆盖报表; -openmp intel C 编译器使用的OPENMP编译选项
CFLAGS += -Wall -O2
NVCCFLAGS += -O2
ifeq ($(MODE),Debug)
CFLAGS := $(filter-out -O1 -O2 -O3, $(CFLAGS))
NVCCFLAGS := $(filter-out -O1 -O2 -O3, $(NVCCFLAGS))
endif
CXXFLAGS = $(CFLAGS)

# 调试选项 -g, -g2, -g3
DEBUG_FLAG ?= -g
DEBUG_NVCC_FLAG ?= -g -G

# std flag Options: -std=c99,-std=iso9899:199409, -std=c89
STD_FLAG ?= -std=c++0x

# The C program compiler.
CC := gcc

# The C++ program compiler.
CXX ?= g++

# The CUDA program compiler.
NVCC ?= $(CUDA_PATH)/bin/nvcc

# 打开此选项后以C++格式编译C程序
#CC = $(CXX)

# 删除文件的命令
RM ?= rm -f

# ctags命令及选项
CTAGS ?= ctags
CTAGSFLAGS +=-R --c++-kinds=+p --langmap=c++:+.hpp.cu.cuh --fields=+iaS --extra=+q  

# nvprof命令及选项
NVPROF ?= nvprof --profile-from-start-off

##==========================================================================
# 对未指定选项做一些处理
##==========================================================================
ifeq ($(USEGPU),true) #GPU不能与OMP混用
USEOMP=false
endif

ifeq ($(USEOMP),true) 
ifeq ($(CC),gcc)
CFLAGS += -fopenmp
else
CFLAGS += -openmp
endif
endif

#-设置可执行文件名-#
EMPTY =
SPACE = $(EMPTY) $(EMPTY)
CUR_PATH_NAMES = $(subst /,$(SPACE),$(subst $(SPACE),_,$(CURDIR)))
DIR_NAME = $(word $(words $(CUR_PATH_NAMES)),$(CUR_PATH_NAMES))
ifeq ($(DIR_NAME),)
    DIR_NAME = a
endif
APP := $(strip $(APP))
ifeq ($(APP),)
    APP = $(DIR_NAME)
endif
ifeq ($(OS),Windows_NT)
    APP := $(APP).exe
else
    APP := $(APP).out
endif

#设置系统字长
ifeq ($(OS_SIZE),32)
    NVCCFLAGS   += -m32
else
    NVCCFLAGS   += -m64
endif

#-DEBUG模式下添加选项-#
ifeq ($(MODE),Debug)
    CFLAGS += $(DEBUG_FLAG)
    NVCCFLAGS += $(DEBUG_NVCC_FLAG)
    MACRO_DEFINE+= -D THRUST_DEBUG
else
    MACRO_DEFINE+= -D NDEBUG
endif

#-源文件目录-#
ifeq ($(SRC_DIRS),)
    SRC_DIRS = .
endif

#-添加std选项-#
ifneq ($(STD_FLAG),)
    CFLAGS += $(STD_FLAG)
endif

#宏定义
MACRO_DEFINE += -D GPU=$(USEGPU) -D OPENMP=$(USEOMP) -D NORAND=$(NORAND)
##==========================================================================
# 下面的基本不要修改,自动化执行
##==========================================================================
# foreach 对每一个后缀名做wildcard(展开通配符得到实际的源文件)处理 
FILETYPE += $(SRCEXTS) $(HDREXTS)
SOURCES = $(foreach d,$(SRC_DIRS),$(wildcard $(addprefix $(d)/*,$(SRCEXTS))))
HEADERS = $(foreach d,$(SRC_DIRS),$(wildcard $(addprefix $(d)/*,$(HDREXTS))))
SYNC_INCLUDE = $(foreach d,$(FILETYPE),--include=*$(d))
SRC_CXX = $(filter-out %.c %.cu,$(SOURCES))
SRC_CUDA = $(filter %.cu,$(SOURCES))
OBJS = $(join $(dir $(SOURCES)), $(addprefix ., $(addsuffix .o, $(basename $(notdir $(SOURCES)) ))))
DEPS = $(join $(dir $(SOURCES)), $(addprefix ., $(addsuffix .d, $(basename $(notdir $(SOURCES)) ))))

COMPILE_c = $(CC) $(CPPFLAGS) $(CFLAGS) $(LIBS) $(MACRO_DEFINE) -c
COMPILE_cxx = $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LIBS) $(MACRO_DEFINE) -c
COMPILE_cuda = $(NVCC) $(NVCCFLAGS) $(GENCODE_FLAGS) $(CUDA_LIBS) $(MACRO_DEFINE) -c
LINK_c = $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS)
LINK_cxx = $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS)
PRODUCTS = $(APP) $(OBJS) $(DEPS) 

#==================================================================
.PHONY: all objs clean distclean garbageclean rebuild

# Delete the default suffixes
.SUFFIXES:

all: $(APP)

# 生成依赖文件(.d).
.%.d:%.c
	@$(CC) -MM -MT $(basename $@).o -MF $@ $(CFLAGS) $<
                                 
.%.d:%.C                         
	@$(CC) -MM -MT $(basename $@).o -MF $@ $(CXXFLAGS) $<
                                 
.%.d:%.cc                        
	@$(CC) -MM -MT $(basename $@).o -MF $@ $(CXXFLAGS) $<
                                 
.%.d:%.cpp                       
	@$(CC) -MM -MT $(basename $@).o -MF $@ $(CXXFLAGS) $<
                                 
.%.d:%.CPP                       
	@$(CC) -MM -MT $(basename $@).o -MF $@ $(CXXFLAGS) $<
                                 
.%.d:%.c++                       
	@$(CC) -MM -MT $(basename $@).o -MF $@ $(CXXFLAGS) $<
                  
.%.d:%.cp         
	@$(CC) -MM -MT $(basename $@).o -MF $@ $(CXXFLAGS) $<
                                           
.%.d:%.cxx                                 
	@$(CC) -MM -MT $(basename $@).o -MF $@ $(CXXFLAGS) $<
.%.d:%.cu
	@$(CC) -MM -MT $(basename $@).o -MF $@ -x c++ $(CXXFLAGS) $<


# 生成目标文件(.o).
objs:$(OBJS)

.%.o:%.c $(DEPS_MORE)
	$(COMPILE_c) $< -o $@
.%.o:%.C $(DEPS_MORE)
	$(COMPILE_cxx) $< -o $@
.%.o:%.cc $(DEPS_MORE)
	$(COMPILE_cxx) $< -o $@
.%.o:%.cpp $(DEPS_MORE)
	$(COMPILE_cxx) $< -o $@
.%.o:%.CPP $(DEPS_MORE)
	$(COMPILE_cxx) $< -o $@
.%.o:%.c++ $(DEPS_MORE)
	$(COMPILE_cxx) $< -o $@
.%.o:%.cp $(DEPS_MORE)
	$(COMPILE_cxx) $< -o $@
.%.o:%.cxx $(DEPS_MORE)
	$(COMPILE_cxx) $< -o $@
.%.o:%.cu $(DEPS_MORE)
	$(COMPILE_cuda) $< -o $@

# 链接成可执行文件
$(APP):$(OBJS)
ifeq ($(SRC_CUDA),) # C/C++ program
ifeq ($(SRC_CXX),) # C program
	$(CC) $(CPPFLAGS) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) 
else # C++ program
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(OBJS)  -o $@ $(LDFLAGS)
endif
else # CUDA program
	$(NVCC) $(NVCCFLAGS) $(OBJS) -o $@ $(LDFLAGS) $(CUDA_LDFLAGS)
endif
	@echo
	@echo Type ./$@ to execute the program.
ifeq ($(NORAND),true)
	@echo "========================================"
	@echo "       Waring: no rand mode !!!"
	@echo "========================================"
endif

# 包含进文件依赖
-include $(DEPS)

clean:
	$(RM) $(OBJS) $(APP)

distclean:
	$(RM) $(PRODUCTS)

garbageclean:
	$(RM) *.o *.save *.gcov *.gcda *.gcno *.orig *.d tags gmon.out   

rebuild: clean all 


#######################################
#          常用函数
######################################
.PHONY: ctags profiler excute vim tar download upload help show binary-name
# 生成ctags
ctags: $(HEADERS) $(SOURCES)
	$(CTAGS) $(CTAGSFLAGS) $(HEADERS) $(SOURCES)

# 性能分析
profiler: $(APP)
	$(NVPROF) ./$(APP)

excute:$(APP)
	@mkdir -p data
	@time ./$(APP) | tee data/output.dat

# 用vim打开所有文件
vim: $(HEADERS) $(SOURCES)
	vim $(HEADERS) $(SOURCES)

# 打包所有代码及数据文件
tar: $(SOURCES) $(HEADERS)
	tar -zcvf $(DIR_NAME)$$(date +%F).tar.gz $(SOURCES) $(HEADERS) $(firstword $(MAKEFILE_LIST)) data

download:
	@echo -n $(SSH_HOST_ALL) |\
	    awk '{for(i = 1; i<NF+1; i++) {\
		    sub(/{.*}/,"",$$i);\
		    print i"."$$i;\
		 } \
		 ORS=""; \
		 print "download from...?";}'
	@REMOTE_HOST=$$(echo -n $(SSH_HOST_ALL) |\
	    awk '{\
		getline var < "/dev/tty";\
		host=substr($$var,1,length($$var)-1);\
		sub(/{/,":/",host);\
		print host}');\
	    REMOTE_HOST_DIR=$$(echo $$(pwd) | sed "s#$(SSH_HOST_DIR)#$$REMOTE_HOST#");\
	    rsync $(SYNC_OPTIONS) $(SYNC_OPTIONS_DOWNLOAD) --exclude=*backup.bak/ --include=*/ $(SYNC_INCLUDE) --exclude=* \
	    $$REMOTE_HOST_DIR/ $$(pwd)/

upload:                                                        
	@echo -n $(SSH_HOST_ALL) |\
	    awk '{for(i = 1; i<NF+1; i++) {\
		    sub(/{.*}/,"",$$i);\
		    print i"."$$i;\
		 } \
		 ORS=""; \
		 print "upload to...?";}'
	@REMOTE_HOST=$$(echo -n $(SSH_HOST_ALL) |\
	    awk '{\
		getline var < "/dev/tty";\
		host=substr($$var,1,length($$var)-1);\
		sub(/{/,":/",host);\
		print host}');\
	    REMOTE_HOST_DIR=$$(echo $$(pwd) | sed "s#$(SSH_HOST_DIR)#$$REMOTE_HOST#");\
	    rsync $(SYNC_OPTIONS) $(SYNC_OPTIONS_UPLOAD) --exclude=*backup.bak/ --include=*/ $(SYNC_INCLUDE) --exclude=* \
	    $$(pwd)/ $$REMOTE_HOST_DIR/

help:
	@echo
	@echo "Usage: make [TARGET]"
	@echo "TARGETS:"
	@echo " all   \t  (=make) compile and link."
	@echo " objs   \t  compile only (no linking)."
	@echo " ctags   \t  create ctags for VI editor."
	@echo " clean   \t  clean objects and the executable file."
	@echo " distclean   \t  clean objects and executable ."
	@echo " garbageclean   \t  clean some garbage."
	@echo " excute   \t  excute the program."
	@echo " rebuild   \t  clean and make all again."
	@echo " upload/download   \t  upload or download the files via rsync."
	@echo " show   \t  show variables (for debug use only)."
	@echo " help   \t  print this message."
	@echo " use command 'tar -zcvf ../code.tar.gz ../hg' to tar the source code and untar it with 'tar -xf code.tar.gz'"
	@echo

show:
	@echo
	@echo "OS   \t  :" $(OS)
	@echo "APP   \t  :" $(APP)
	@echo "MODE   \t  :" $(MODE)
	@echo "SRC_DIRS   \t  :" $(SRC_DIRS)
	@echo "HEADERS   \t  :" $(HEADERS)
	@echo "SOURCES   \t  :" $(SOURCES)
	@echo "SRC_CXX   \t  :" $(SRC_CXX)
	@echo "OBJS   \t  :" $(OBJS)
	@echo "COMPILE_c   \t  :" $(COMPILE_c)
	@echo "COMPILE_cxx   \t  :" $(COMPILE_cxx)
	@echo "LINK_c   \t  :" $(LINK_c)
	@echo "LINK_cxx   \t  :" $(LINK_cxx)
	@echo "PRODUCTS   \t  :" $(PRODUCTS)
	@echo "rsync   \t  :" rsync $(SYNC_OPTIONS) --exclude=*/backup.bak/ --include=*/ $(SYNC_INCLUDE) --exclude=* remote://path host://path
	@echo

binary-name:
	@echo $(APP)

There are some drawbacks for this Makefile like only one main function can exist in the code tree.