1. 前言
- 在做什么事情之前,有一个好的工具是很重要的,这可能会起到事半功倍的效果,就如同磨刀不误砍柴功,有一曲同工之妙。
- 在我们编写C/C++工程时,也许我们会选择一个流行的IDE,如vs202x / vscode, CLoin 或者 Codeblocks,使用IDE的好处是可以不用写编译程序的脚本,只需要run code 就ok,操作方便,受人青睐。
- 如上的IDE在Win上比较受欢迎,但是在工作中,基本都是在Linux下开发,拥有一个通用的Make 脚本是很重要的,需编写Makefile;另外比Make 更高级一点的还有CMake, 需要编写CMakeLists.txt。(但是在工作中,基本上makefile都是写好了的,我们仅需看得懂,能够以葫芦画瓢,能修改就好)
- 另外工作中可能会使用的很多的shell命令,简单说就是在终端输入的命令,比如简单的ls, cd等;我们可以建立一个shell脚本,比如命名为s,可以将一些复杂经常使用的命令写到s脚本中(比如,编译某某工程,pull code,push code等等),以便以后使用(./s -b xx)。
2. Make 通用模板
a. 简单工程,根目录下仅有.c和.h
#Makefile
# make -f make_base.mk
# 编译器
CC = gcc
# 编译选项
CFLAGS = -Wall
# 目标文件
TARGET = myprogram
# 源文件
# 当前目录下的.c
SRCS = $(wildcard *.c)
# 头文件
INC = -Imodule1/inc
# INC += -Ipath/to/include2
CFLAGS += $(INC)
# LIB
# LIB = -Lpath/to/lib1
# LIB += -Lpath/to/lib2
# CFLAGS += $(LIB)
#添加宏定义
# CFLAGS += -DANDROID_OS
#添加其他模块的make文件
# include /path/xxx/makefile
# 对象文件
OBJS = $(SRCS:.c=.o)
# 默认构建目标
all: $(TARGET)
# 生成可执行文件
$(TARGET): $(OBJS)
$(CC) -o $@ $^
mkdir -p build && mv $(OBJS) build
mkdir -p bin && mv $(TARGET) bin
# 生成目标文件
%.o: %.c
$(CC) $(CFLAGS) -c $<
# 清理生成的文件
clean:
rm -f $(OBJS) $(TARGET)
b. 复杂工程,根目录下有main.c ./module1/src ./module1/inc等
#Makefile
# 编译器
CC = gcc
# 编译选项
CFLAGS = -Wall
# 目标文件
TARGET = mp
OBJ_DIR = build
# 源文件
# 当前目录下的.c
SRCS = $(wildcard *.c)
#其他目录下的.c
SRCS += $(wildcard module1/src/*.c)
# 头文件
CFLAGS += -Imodule1/inc
#CFLAGS += -Ipath/to/include2
# LIB
#CFLAGS += -Lpath/to/lib1
#CFLAGS += -Lpath/to/lib2
#添加宏定义
#CFLAGS += -DANDROID_OS
#添加其他模块的make文件
include ./module2/module.mk
# 对象文件和中间文件目录
# 将 SRCS 中的 .c 文件名转换为 $(OBJ_DIR)/%.o 格式的目标文件路径
OBJS = $(addprefix $(OBJ_DIR)/, $(notdir $(SRCS:.c=.o)))
#OBJS = $(patsubst %.c,$(OBJ_DIR)/%.o,$(notdir $(SRCS)))
# 默认构建目标
all: $(TARGET)
# 生成可执行文件
$(TARGET): $(OBJS)
$(CC) -o $@ $^
# 生成根目录下的目标文件
$(OBJ_DIR)/%.o: %.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -c $< -o $@
# 生成其他目录下的目标文件
$(OBJ_DIR)/%.o: module1/src/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -c $< -o $@
# 创建中间文件目录
$(OBJ_DIR):
mkdir -p $(OBJ_DIR)
# 清理生成的文件
clean:
rm -rf $(OBJ_DIR) $(TARGET)
c.升级b版本的,简化了生成.o链接文件的过程,现在仅需要添加头文件和源文件即可编译ok(主要却别就是建立的一个文件夹@mkdir -p $(dir $@))
#Makefile
# 编译器
CC = g++
# 编译选项
CFLAGS = -Wall
# 目标文件
TARGET = mp
OBJ_DIR = build
# 源文件
# 当前目录下的.cpp
SRCS = $(wildcard *.cpp)
#其他目录下的.cpp
SRCS += $(wildcard module1/src/*.cpp)
SRCS += $(wildcard module2/src/*.cpp)
# 头文件
CFLAGS += -Imodule1/inc
CFLAGS += -Imodule2/inc
#CFLAGS += -Ipath/to/include2
# LIB
#CFLAGS += -Lpath/to/lib1
#CFLAGS += -Lpath/to/lib2
#添加宏定义
#CFLAGS += -DANDROID_OS
# 对象文件和中间文件目录
OBJS = $(SRCS:%.cpp=$(OBJ_DIR)/%.o)
# 默认构建目标
all: $(TARGET)
# 生成可执行文件
$(TARGET): $(OBJS)
$(CC) -o $@ $^
# 生成根目录下的目标文件
$(OBJ_DIR)/%.o: %.cpp | $(OBJ_DIR)
#创建文件夹很关键,不然会fail
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
# 创建中间文件目录
$(OBJ_DIR):
mkdir -p $(OBJ_DIR)
# 清理生成的文件
clean:
rm -rf $(OBJ_DIR) $(TARGET)
编译:该文件位于工程根目录,并在当前目录下执行make 即可编译;或者指定目录:make -C <dir>, 或者指定文件make -f xx.mk
如果想要添加别的工程的源文件,比如当前工程需要使用curl(这个是给的源文件)工程的东西,可以在curl中建立一个makefile, 里面包含器头文件和*.c文件,但是变量名要和主工程makefile中的一致(如INC, LIB,CFLAGS等),最后在主工程makefile中 include /path/xxx/makefile
3. 关键部分解释
### 源文件
SRCS = $(wildcard *.c)
这一行使用了 wildcard 函数,它会匹配当前目录下所有以 .c 结尾的文件,
并将结果保存在变量 SRCS 中。这里假设所有的源文件都是以 .c 结尾的。
### 对象文件
OBJS = $(SRCS:.c=.o)
这一行使用了替换规则,将变量 SRCS 中的 .c 后缀替换为 .o 后缀,
并将结果保存在变量 OBJS 中。这样就得到了所有的对象文件(即将编译后的源文件)。
### 生成可执行文件
$(TARGET): $(OBJS)
$(CC) -o $@ $^
这一行定义了生成可执行文件的规则,它依赖于变量 $(OBJS),也就是所有的对象文件。
$@ 表示目标文件名,$^ 表示所有的依赖文件。这条规则使用 $(CC)
编译器将所有的对象文件链接起来生成最终的可执行文件。
### 生成目标文件
%.o: %.c
$(CC) $(CFLAGS) -c $<
这一行定义了生成目标文件的规则,它使用了模式规则。%.o 表示任意的 .o 文件,
%.c 表示与之对应的同名 .c 文件。这条规则使用 $(CC) 编译器编译对应的 .c 文件,
并使用 $(CFLAGS) 编译选项进行编译。-c 选项表示只编译成目标文件而不进行链接。
$< 表示第一个依赖文件,即对应的 .c 文件。
### 生成其他目录下的目标文件
$(OBJ_DIR)/%.o: module1/src/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -c $< -o $@
# 创建中间文件目录
$(OBJ_DIR):
mkdir -p $(OBJ_DIR)
“| $(OBJ_DIR)”是一个前置条件(order-only prerequisite),这个写法可以确保在执行其他规则之前,始终会先创建 $(OBJ_DIR) 目录,即使它已经存在也会执行创建目录的操作。
### 对象文件和中间文件目录
OBJS = $(addprefix $(OBJ_DIR)/, $(notdir $(SRCS:.c=.o)))
#OBJS = $(patsubst %.c,$(OBJ_DIR)/%.o,$(notdir $(SRCS)))
将 SRCS 中的 .c 文件名转换为 $(OBJ_DIR)/%.o 格式的目标文件路径, 上面两句的效果是一样的;$(notdir $(SRCS:.c=.o))表示先将SRCS中的%.c转为%.o, 然后将前面的目录项去掉,如./src/tool.c -> tool.o
4. Cmake 示例
//CMakeLists.txt
# 设置最低的 CMake 版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(MyProject)
/*
# 添加所有源文件
set(SOURCES
src/main.c
src/util.c
src/other_file.c
)
*/
# 添加所有 .c 源文件
file(GLOB SOURCES "src/*.c")
# 添加可执行文件
add_executable(myprogram ${SOURCES})
# 添加头文件搜索路径
include_directories(include)
# 添加链接库搜索路径
link_directories(lib)
# 添加链接库
target_link_libraries(myprogram mylibrary)
# 指定编译选项
set(CMAKE_C_FLAGS "-Wall -O2")
# 设置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
编译:在工程根目录建立一个build 目录,然后在build目录下执行cmake …/ && make ('/'前面两个点,表示上一级目录,不是三个点哦)
5. Shell 工具脚本
a. 简单示例
#!/bin/bash
# s
#使用说明
usage() {
echo "./s -build [option] : "
echo "./s -pull [option] : "
echo "./s -push [option] : "
}
#主函数,里面可任意添加
main() {
if [ "$1" == "-build" ]; then
if [ "$2" == "-c" ]; then
echo make clean
else
echo make path/xxx/makefile
fi
elif [ "$1" == "-pull" ]; then
echo git clone www.xxxx.xxx
elif [ "$1" == "-push" ]; then
echo git add .
echo git commit
echo git push
else
usage
fi
}
# for test
#aa="-build -c"
#main $aa
# normal "$@" 表示命令行参数传入main
main "$@"
b. 文末实际案列,用于编译运行工程
#!/bin/bash
usage() {
echo "使用说明"
echo "./s <-b> <project_name> build project"
echo "./s <-c> <project_name> clean build & target"
echo "./s <-r> <project_name> run program"
}
main() {
if [ $1 == "-b" ]; then
if [ -n "$2" ]; then #$2有内容
cd $2
make clean
make
else
usage
fi
elif [ $1 == "-c" ]; then
if [ -n "$2" ]; then
cd $2
make clean
else
usage
fi
elif [ $1 == "-r" ]; then
if [ -n "$2" ]; then
./$2/mp
else
usage
fi
else
usage
fi
}
main "$@"
6. 用来编译运行工程的工具示例下载
点我下载
如下是工程目录结构:包含C和C++工程, 如果要混合的工程需要自己捣鼓
文章评论