使用cmake构建一个大型项目框架

文章目录

  • 使用CMake构建一个大型项目工程
  • 1. 大型工程目录结构介绍
    • 1.1 工程目录结构介绍
    • 1.2 工程目录说明(我是这样设计的,你们也可以参考类似这样设计)
    • 1.3 最外层CMakeLists.txt说明
    • 1.4 业务逻辑中的CMakeLists.txt说明
      • 1.4.1 source中的目录结构
      • 1.4.2 CMakeLists.txt说明
    • 1.5 测试模型中的的CMakeLists.txt说明
      • 1.51 unit_test中的目录结构
  • 2. 编译脚本说明
  • 3. 工程地址

使用CMake构建一个大型项目工程

在上文中,我们已经学会用CMake去构建一个项目。接来下我对这个工程进一步的完善,使其能应用在更加的项目开发当中。构建一个项目工程,首先要知道自己的要用这个工程来做什么。我是一名嵌入式软件开发工程师。日常的开发有如下需求:

  1. 开发的平台经常更换,有时使用st的芯片,有时使用nxp的芯片,还有国产的芯片等,这些开发平台是经常需要改变。使用这些芯片的时候,我要使用他们的外设接口(如 spi i2c uart )去操作各种传感器。各种传感器都各种类型品牌,比如加速传感器就有几十种,经常需要测试那种比较好和便宜,各种传感器芯片都要编写一个测试单元去测试,除了板载的测试,我们还需要经常网络的测试,各种类型库的测试,,最终单元测试完成后才能加入我的源码中,进行商务逻辑编写。
  2. 在开发过程中,难免会使用一些库,比如mqtt,curl,json等
  3. 经常会使用交叉编译工具链编译之后传到对应的硬件平台。这里弄一个编译之后自动传到对应的脚本

要优化好上面的脚本也是很大的工作量,接下来我将给大家介绍一下我自己常用的模型是怎么样的。

1. 大型工程目录结构介绍

1.1 工程目录结构介绍

为了简介,我删除了很多我自己定义的文件夹,留下的都是自己定义的

.
├── bin
│   └── CMake_Project
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   ├── install_manifest.txt
│   ├── Makefile
│   └── source
├── buildcfg
├── build.sh
├── CMakeLists.txt
├── doc
│   └── README.md
├── log
│   └── README.md
├── README.md
├── script
│   └── README.md
├── source
│   ├── CMakeLists.txt
│   ├── main.cpp
│   └── System_Frame
├── third_lib
│   └── mqtt_lib
└── unit_test
    ├── board_test
    └── net_test

1.2 工程目录说明(我是这样设计的,你们也可以参考类似这样设计)

1.bin
		该目录存放着程序的执行文件,如单元测试的执行文件以及总工程项目的执行文件。

2.build
		该目录存放CMake构建后的缓存文件。

3.doc
		该目录存放工程相关说明文档。

4.script
		该目录存放其他辅助的脚本。

5.source
		该目录存放主体业务源码。

6.third_lib
		该目录存放第三方使用的库。

7.unit_test
		该目录存放单元测试工程。

8.CMakeLists.txt
		总工程构建CMake文件。

9.build.sh
		总工程编译脚本文件。

10.buildcfg
		build.sh的配置文件。

11. log
     存放修改的详细内容

1.3 最外层CMakeLists.txt说明

cmake_minimum_required(VERSION 3.2.0)

#set the project name
set(LISIN_MTR "CMake_Project")

set(CMAKE_BUILD_TYPE "Debug") #Release 如果是发行版本就更换Debug为Release 
if(CMAKE_BUILD_TYPE STREQUAL Debug)
    add_definitions(-D_DEBUG) #添加D_DEBUG编译参数
endif()

#设置编译参数
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lpthread -std=gnu99 -g -o0") #
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}  -W -Wall -Wextra -g2 -ggdb -o0 -lpthread")

# 设置编译工具链
#set(COMPILER_PREFIX "arm-linux-")
#set(CMAKE_C_COMPILER ${COMPILER_PREFIX}gcc)
#set(CMAKE_CXX_COMPILER ${COMPILER_PREFIX}g++)

#下面这部分是可修改部分,根据自己的实际工程要做什么去定义,我下面只是给你们一个参考
#################################################################
# 定义头文件路径对应的名字,方便等下去设定
#通用与辅助
set(SYS_FRAME_H     "${CMAKE_SOURCE_DIR}/source/System_Frame")

#库文件路径添加
file(GLOB_RECURSE Mqtt_lib ${THIRLD_LIB_PATH}/mqtt_lib/lib/*.so*)

#项目编译选择,通过选择开关来进行编译选项,通过这种方式去选择编译测试模块中的哪一个
# 之后需要添加,
option(Main_Source "main pro"     ON)
option(BOARD_TEST  "board_test"   OFF)

#添加子工程目录
if(Main_Source)
add_subdirectory(${CMAKE_SOURCE_DIR}/source)
endif()

#测试程序
if(BOARD_TEST)
option(IIC_TEST  "UART"   OFF)
option(GPIO_TEST "GPIO"  OFF)
add_subdirectory(${CMAKE_SOURCE_DIR}/unit_test/board_test)
endif()

1.4 业务逻辑中的CMakeLists.txt说明

1.4.1 source中的目录结构

.
├── CMakeLists.txt
├── main.cpp
└── System_Frame
    ├── print_cmake.cpp
    └── print_cmake.h

1.4.2 CMakeLists.txt说明

# 查找当前目录下的所有源文件
aux_source_directory("${PROJECT_SOURCE_DIR}/source/System_Frame" DIR_SOURCE_SYS_FRAME)
aux_source_directory("${PROJECT_SOURCE_DIR}/source" DIR_SOURCE_SRCS)

# 指定生成目标
add_executable( ${LISIN_MTR} 
                ${DIR_SOURCE_SYS_FRAME}
                ${DIR_SOURCE_SRCS}
              )

message(STATUS "SYS_FRAME_H: " ${SYS_FRAME_H})

#头文件包含
target_include_directories(${LISIN_MTR} PRIVATE
                           ${SYS_FRAME_H}
                           )

#连接库                         
target_link_libraries(${LISIN_MTR} PUBLIC ${Mqtt_lib})

#执行文件的安装位置
install(TARGETS ${LISIN_MTR} RUNTIME DESTINATION ${PROJECT_SOURCE_DIR}/bin)

## 主要做了三件事
# 1. 设置源文件的目录,获取所有源文件
# 2. 设置头文件目录
# 3. 链接库
# 4. 定义可执行文件的安装位置

1.5 测试模型中的的CMakeLists.txt说明

1.51 unit_test中的目录结构

unit_test:这里相当于一个独立的单元,完全由自己编写,不影响主程序,在最外层CMakeLists.txt开关调为ON就编译的这里的程序

.
├── board_test
│   ├── gpio
│   │   └── CMakeLists.txt
│   └── uart
│       └── CMakeLists.txt
└── net_test

# 1. add_subdirectory()使用的就是增加子目录的方式,然后每个子目录干自己的事
# 2. 里面的每个文件夹都有单独的测试单元,只有测试好了才放到主程序,

2. 编译脚本说明

这个脚本主要的作用是一键编译工程,解决了局域网网内文件的传输,可以可以执行文件放到局域网的目标设备中,对于嵌入式开发会很方便

 #用于工程编译以及传输执行固件到局域网下的目标板,使用如下:
 #
 # ./build,sh -u root -p root -i 192.168.1.20 -s lisinmtr -h /usr/local/lisin/bin
 #
 # -u :ssh用户
 # -p :ssh用户密码
 # -i :ssh用户地址
 # -s :需要传输的源文件
 # -h :ssh用户的传输目标地址 
 #
 # -u -p -i -s -h 参数都不加上去的情况下使用默认参数:
 #login_usr_default, login_pswd_default, login_ip_default, exe_bin_default, lisin_bin_path_default
 #
 #注意:修改后的参数会保存在[buildcfg]配置文件里,每次运行都会先读取配置文件里的信息!!!!
 #
 # @*******: ********************************************************
### 

#!/bin/bash

#开发主机相关信息,当前build.sh放的地方
dirCurrent=`pwd`

#放CMake编译生成文件的地方
dirCompile=$dirCurrent/build

#默认要传输的文件设置,传输的文件名
exe_bin_default=lisinmtr

#局域网的设备 信息
#login_user ssh的登录账户
login_usr_default="root"
#login_passwd ssh的登录密码
login_pswd_default="root"
#login_ip ssh的ip
login_ip_default="192.168.1.20"
#工作目录 上面传输的文件要放的位置
lisin_bin_path_default="/usr/local/lisin/bin"

# Initial directory
rm -rf $dirCompile  #每次都重新编译,删除build文件
mkdir -p $dirCompile #创建build文件
cd $dirCompile

# Run cmake
cmake $dirCurrent  #编译cmake

# Run make
cmd_res=$? #判断编译的时候是否跟着参数

if [ $cmd_res -eq 0 ];then
    echo "Make \`Makefile\` complete."
    make -j4
else
    echo "[Err:$cmd_res] \`cmake\` faild."
    exit 1
fi

# make install
cmd_res=$?

if [ $cmd_res -eq 0 ];then
    echo "Compilation complete."
    make install #生成的文件安装
else
    echo "[Err:$cmd_res] \`make\` faild."
    exit 2
fi

# Copy executable to NUC980
cmd_res=$?

if [ $cmd_res -eq 0 ];then

    cd $dirCurrent

    # 读取配置文件
    cfg_file="$dirCurrent/buildcfg"

		# 检测配置文件是否存在
    if [ ! -f $cfg_file ]; then
				
				#如果不存在,赋值上面默认的数据
        echo "touch $cfg_file!!!"
        echo "login_user=$login_usr_default" > $cfg_file
        echo "login_pswd=$login_pswd_default" >> $cfg_file
        echo "login_host=$login_ip_default" >> $cfg_file
        echo "exe_bin=$exe_bin_default" >> $cfg_file
        echo "dest_path=$lisin_bin_path_default" >> $cfg_file

        _exe_bin=$exe_bin_default
        _login_user=$login_usr_default
        _login_pswd=$login_pswd_default
        _login_host=$login_ip_default
        _dest_path=$lisin_bin_path_default

    else
				#如果存在读取这些值
        echo "read buildcfg..."

				#找出配置文件中的值
        _login_user=`sed '/^login_user=/!d;s/.*=//;s/\r//g' $cfg_file`
        _login_pswd=`sed '/^login_pswd=/!d;s/.*=//;s/\r//g' $cfg_file`
        _login_host=`sed '/^login_host=/!d;s/.*=//;s/\r//g' $cfg_file`
        _exe_bin=`sed '/^exe_bin=/!d;s/.*=//;s/\r//g' $cfg_file`
        _dest_path=`sed '/^dest_path=/!d;s/.*=//;s/\r//g' $cfg_file`

				#如果某个值为空,使用默认的值,保存默认的值到配置文件
        if [ "$_exe_bin" = "" ]; then
            echo "login_user=$login_usr_default" >> $cfg_file
            _exe_bin=$exe_bin_default
        fi
        

        if [ "$_login_user" = "" ]; then
            echo "login_pswd=$login_pswd_default" >> $cfg_file
            _login_user=$login_usr_default
        fi

        if [ "$_login_pswd" = "" ]; then
            echo "login_host=$login_ip_default" >> $cfg_file
            _login_pswd=$login_pswd_default
        fi

        if [ "$_login_host" = "" ]; then
            echo "exe_bin=$exe_bin_default" >> $cfg_file
            _login_host=$login_ip_default
        fi

        if [ "$_dest_path" = "" ]; then
            echo "dest_path=$lisin_bin_path_default" >> $cfg_file
            _dest_path=$lisin_bin_path_default
        fi

    fi

    #参数输入
    exe_bin="${_exe_bin}"
    login_user="${_login_user}"
    login_pswd="${_login_pswd}"
    login_host="${_login_host}"
    dest_path="${_dest_path}"
  

    #读取外部输入参数
    while getopts ":u:p:i:s:h:" opt
    do
        case $opt in
            u)
            login_user=$OPTARG
            ;;
            p)
            login_pswd=$OPTARG
            ;;
            i)
            login_host=$OPTARG
            ;;
            s)
            exe_bin=$OPTARG
            ;;
            h)
            dest_path=$OPTARG
            ;;
            ?)
            echo "args error,using default args!!!!"
            ;;
        esac
    done

    #更新配置文件
    if [ "$exe_bin" != "$_exe_bin" ]; then
        echo "replace exe_bin!"
        sed -i "/^exe_bin=/c\exe_bin=${exe_bin}" $cfg_file
    fi

    if  [ "$login_user" != "$_login_user" ]; then
        echo "replace login_user!"
        sed -i "/^login_user=/c\login_user=${login_user}" $cfg_file
    fi

    if  [ "$login_pswd" != "$_login_pswd" ]; then
        echo "replace login_pswd!"
        sed -i "/^login_pswd=/c\login_pswd=${login_pswd}" $cfg_file
    fi

    if  [ "$login_host" != "$_login_host" ]; then
        echo "replace login_host!"
        sed -i "/^login_host=/c\login_host=${login_host}" $cfg_file
    fi
       
    if  [ "$dest_path" != "$_dest_path" ]; then
        echo "replace dest_path!"
        sed -i "/^dest_path=/c\dest_path=${dest_path}" $cfg_file
    fi

    #传输的路径一般为主目录下的bin目录,因为cmake会把生成的执行文件通过make install安装到固定的位置bin目录下。
    dirbin=${dirCurrent}/bin/${exe_bin} 
    exe_bin_bkp=${exe_bin}_bkp

    # 判断是否安装 sshpass , 若未安装则安装
    which "sshpass" > /dev/null
    if [ $? == 0 ]; then
        echo "\`sshpass\` Installed. Start copy file..."
    else
        sudo apt-get -y install sshpass 
    fi

#这是sshpass的使用
## 免密码登录
#$ sshpass -p password ssh username@host

# 远程执行命令
#$ sshpass -p password ssh username@host 

# 通过scp上传文件
#$ sshpass -p password scp local_file root@host:remote_file 

# 通过scp下载文件
#$ sshpass -p password scp root@host:remote_file local_file

    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
    echo "Copying $dirbin to $login_user@$login_host[$login_pswd]:$dest_path ..."
    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
    

    # CmdKill="ps | grep "${exe_bin}" | grep -v grep | awk '{print $1}' | xargs kill -9" 

    # sshpass -p $login_pswd ssh -o StrictHostKeyChecking=no $login_user@$login_host "$CmdKill"
		# 执行创建文件夹指令
    sshpass -p $login_pswd ssh -o StrictHostKeyChecking=no $login_user@$login_host "mkdir -p $dest_path"
		# 把之前的可执行文件修改名字
    sshpass -p $login_pswd ssh -o StrictHostKeyChecking=no $login_user@$login_host "mv $dest_path/$exe_bin $dest_path/$exe_bin_bkp"
		# 把之前的可执行文件修改名字,把可执行文件传输过去
    sshpass -p $login_pswd scp -o StrictHostKeyChecking=no "$dirbin" $login_user@$login_host:"$dest_path"

else
    echo "[Err:$cmd_res] \`make\` faild."
    exit 3

fi

3. 工程地址

  • 我在github上已经分享了我的工程,欢迎指出问题也可以提出优化空间,文档中可能讲的不系,但是CMake的语法比较简单,不懂的可以查一下资料,或者看看我上一篇文章,基本都有介绍到。

你可能感兴趣的:(CMake,c++,visual,studio,开发语言)