首页 服务器系统 Linux

linux下使用CMake编译项目工程

简介


CMake 是一个跨平台的项目构建工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出 Makefile 或者 vs项目文件,CMake 并不直接建构出最终的软件,而是产生标准的建构档(如 Makefile 或 vs项目文件)

本文讨论linux下编译。

目录结构

项目主目录存在一个CMakeLists.txt文件,两种目录结构方式:

1.子文件夹下未含有CMakeLists.txt,主目录将子目录所有编译的规则写到主目录的CMakeLists.txt中。

2.子文件夹下含有CMakeLists.txt文件,主目录通过添加子目录方式。

编译方式

cd到CMakeLists.txt所在主目录

1.内部编译

cmake ./
make

2.外部编译

mkdir build
cd build
cmake ../
make

常用语法

demo

├── CMakeLists.txt
├── hello.cpp
└── testcmake.cmake

testcmake.cmake

function(test_func src_list)
   message("1111 ${src_list}") 
   list(APPEND src_list a) 
   message("1111 ${src_list}") 
   list(LENGTH src_list len)   
   if(len LESS 10)
        test_func("${src_list}")
   endif()
   set( src_list ${src_list} PARENT_SCOPE)
endfunction()

function(test_func_1 src_list)
   message("1111 ${src_list}")
   list(APPEND ${src_list} a)
   message("1111 ${src_list}")
   list(LENGTH ${src_list} len)
   if(len LESS 10)
        test_func("${src_list}")
   endif()
   set( ${src_list} ${${src_list}} PARENT_SCOPE)
endfunction()

CMakeLists.txt

#设置CMake最低版本
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

#设置项目名和支持语言(语言可以不用设置)
PROJECT(hello C CXX)

LIST(APPEND CMAKE_MODULE_PATH
  "${PROJECT_SOURCE_DIR}/")
include(testcmake)


#打印环境变量
message (STATUS ${PROJECT_SOURCE_DIR}) # 工程的根目录
message (STATUS ${PROJECT_BINARY_DIR}) # 运行cmake命令的目录,通常是${PROJECT_SOURCE_DIR}/build
message (STATUS ${CMAKE_CURRENT_SOURCE_DIR}) # 当前处理的CMakeLists.txt所在的路径
message (STATUS ${CMAKE_CURRENT_BINARY_DIR}) # target编译目录
message (STATUS ${CMAKE_CURRENT_LIST_FILE}) # 输出调用这个变量的CMakeLists.txt的完整路径
message (STATUS ${PROJECT_NAME}) # 返回通过PROJECT指令定义的项目名称
message(STATUS "operating system: ${CMAKE_SYSTEM_NAME}")


#判断系统
IF(WIN32)
        # 打印
        message(STATUS "do something related to ${CMAKE_SYSTEM_NAME}")
ELSEIF(UNIX)
        message(STATUS "do something related to ${CMAKE_SYSTEM_NAME}")
ELSEIF(APPLE)
        message(STATUS "do something related to ${CMAKE_SYSTEM_NAME}")
ELSEIF(LINUX)
        message(STATUS "do something related to ${CMAKE_SYSTEM_NAME}")
ENDIF(WIN32)


# 添加编译参数
if(CMAKE_COMPILER_IS_GNUCXX)
    add_compile_options(-std=c++11)
    message(STATUS "optional:-std=c++11")   
endif(CMAKE_COMPILER_IS_GNUCXX)

# 设置GCC G++(如重新装g++ 低版本linux系统g++不支持c11需要另装)
# SET(CMAKE_C_COMPILER "/usr/local/bin/gcc")
# SET(CMAKE_CXX_COMPILER "/usr/local/bin/g++")

# 设置头文件路径
INCLUDE_DIRECTORIES(
  ${PROJECT_SOURCE_DIR}/
  ${PROJECT_SOURCE_DIR}/../
  /usr/include/mysql/
)

#调用函数
test_func(src_list)
message(">>>>>>> ${src_list}")
test_func_1(src_list_1)
message(">>>>>>> ${src_list_1}")

#for循环
foreach( f ${src_list_1})
    message(STATUS ${f})
endforeach()


# 设置安装路径
SET(APP_INSTALL_PATH "${PROJECT_SOURCE_DIR}/bin")

# 添加宏定义
add_definitions(-DNDEBUG)

# 设置c++编译参数
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -fmessage-length=0")

# 设置生成可执行程序和编译规则
ADD_EXECUTABLE(hello hello.cpp)

# 设置安装规则
INSTALL(TARGETS hello DESTINATION ${APP_INSTALL_PATH})

生成静态库,只需要将 ADD_EXECUTABLE替换成ADD_LIBRARY


实例

单目录单文件项目 (demo0)

├── CMakeLists.txt
└── hello.cpp

# 设置CMake最低版本
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

# 设置项目名 并设置只支持C和C++编译(可以不设置)
PROJECT(hello C CXX)

# 设置编译规则(生成可执行文件)
ADD_EXECUTABLE(hello hello.cpp)

单目录多文件编译(demo1)

├── CMakeLists.txt
├── hello.cpp
├── test.cpp
└── test.h

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(hello C CXX)
ADD_EXECUTABLE(hello hello.cpp test.cpp)

如果这时候有n个cpp,每个都需要写上去,就要每次都要维护CMakeLists.txt,所以就有下面的写法(demo2)

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(hello C CXX)

# 将源文件遍历到一个list中
AUX_SOURCE_DIRECTORY(./ SOURCE_LIST)

# 统一添加一个list
ADD_EXECUTABLE(hello ${SOURCE_LIST})

多目录多文件编译

1.主目标CMakeLists.txt包含子目录编译规则(demo3)

├── CMakeLists.txt
├── hello.cpp
└── test
├── test.cpp
└── test.h


CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

PROJECT(hello)

# 引入头文件目录
include_directories(test) 

AUX_SOURCE_DIRECTORY(./ SOURCE_LIST)

AUX_SOURCE_DIRECTORY(./test TEST_SOURCE_LIST)

ADD_EXECUTABLE(hello ${SOURCE_LIST} ${TEST_SOURCE_LIST})

2.主目标CMakeLists.txt包含子目录编译规则(demo4)

├── CMakeLists.txt
├── hello.cpp
└── test
├── CMakeLists.txt
├── test.cpp
└── test.h

主目录的CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(hello)
include_directories(test) 
AUX_SOURCE_DIRECTORY(./ SOURCE_LIST)

# 添加子目录
ADD_SUBDIRECTORY(test)

ADD_EXECUTABLE(hello ${SOURCE_LIST})

TARGET_LINK_LIBRARIES(hello testLib)

test文件夹下的CMakeLists.txt

AUX_SOURCE_DIRECTORY(. TEST_SOURCE_LIST)
ADD_LIBRARY(testLib ${TEST_SOURCE_LIST})

多项目多层目录编译

实际项目中,不可能遇到这么简单的结构。

按照两种目录结构方式,第一种需要遍历所有的源文件加到CMakeLists.txt中,第二种就需要在所有字目录中添加CMakeLists.txt,这样版本迭代时候,这样每个子目录下都会生成CMakeLists.txt,显然不符合我们的实际需求。

按照第一种方式我们只需要把源文件遍历就行了,然后加到 ADD_EXECUTABLE 或者 ADD_LIBRARY就行了,紧接着我根据这个思路写了一个Python脚本。。。

1.通过python生成CMakeLists.txt 适合CMake一点都不懂的

通过配置cmake.config来配置相关库头文件等等(demo5)

├── cmakelist.py
├── engine
│ ├── cmake.config
│ ├── CMakeLists.txt
│ ├── config.h
│ ├── gameapp
│ │ ├── gameapp.cpp
│ │ └── gameapp.h
│ ├── json
.......
│ └── util
│ └── shared.h
├── loginapp
│ ├── cmake.config
│ ├── CMakeLists.txt
│ ├── loginapp.cpp
│ ├── loginapp.h
│ ├── main.cpp
│ └── module
│ ├── loginhelper.cpp
│ └── loginhelper.h
└── testclient
├── cmake.config
├── CMakeLists.txt
├── main.cpp
├── testclient.cpp
└── testclient.h

cmakelist.py

# -*- coding: utf-8 -*-

import os
import sys
import demjson

enter_str = '\n'

desc_str = '''
(1).type: exe or lib 
(2).g++ gcc 可以不填,由于Centos 6默认g++不支持c++11,需要另外装g++
(3).如果想忽略某个文件夹下在下面建一个cmake.ignore
(4).output_path相对项目路径的
(5).lib:库之间有先有顺序,被依赖的库放到后面 '''

temp_str = '''
{
  "cmake_version":"2.8.12.2",
  "gcc":"/usr/local/bin/gcc",
  "g++":"/usr/local/bin/g++",
  "type": "exe",
  "project":"GateApp",
  "include_path":"./ ../ ./mysql /usr/include/mysql/ ../../../3rd",
  "lib_path": "/usr/lib64/mysql/ ../../../lib ../../../3rd/lib",
  "lib": "ACE z cppunit uuid Common GameConfigData Common Dao Message engine",
  "output_path": "../../../App",
  "options":"-DNDEBUG -O3 -Wall -c -fmessage-length=0 -std=c++11 -fpermissive -lpthread"
}
'''


def write_file_context(dir_path, file_name, context):
    file_path = os.path.join(dir_path, file_name)
    temp_file = open(file_path, "w")
    context += '\n'
    temp_file.write(context)
    temp_file.close()


ignore_dirs = []


def walk_and_modify_cpp(path):
    path_src = ''
    for r, dirs, files in os.walk(path):
        cmake_ignore = os.path.join(r, "cmake.ignore")
        if os.path.isfile(cmake_ignore) or r in ignore_dirs:
            for dd in dirs:
                new_dd = os.path.join(r, dd)
                if new_dd in ignore_dirs:
                    continue
                ignore_dirs.append(new_dd)
                print(new_dd)
            continue
        for f in files:
            if f.endswith(".cpp") or f.endswith(".c") or f.endswith(".pb.cc"):
                new_path = os.path.join(r, f)
                sp_ret = new_path.split(path, 1)
                if len(sp_ret) != 2:
                    print(r)
                    print(f)
                    print(new_path)
                    print(sp_ret)
                new_path_src = sp_ret[1]
                if new_path_src[0] == '/':
                    new_path_src = "." + new_path_src
                else:
                    new_path_src = os.path.join(".", new_path_src)
                path_src += new_path_src + enter_str
    return path_src


def main():
    if len(sys.argv) < 2:
        exit("argv num != 2")
    dir_name = sys.argv[1]
    if not os.path.isdir(dir_name):
        exit("{0} not exist!".format(dir_name))
    json_file = os.path.join(dir_name, "cmake.config")
    if not os.path.isfile(json_file):
        print(desc_str)
        print(temp_str)
        exit("{0} not exist!".format(json_file))
    js = demjson.decode_file(json_file)

    project_name = js["project"]
    exe_type = js["type"]
    include_path = js['include_path']
    lib_path = js['lib_path']
    lib = js['lib']
    output_path = js['output_path']

    if exe_type not in ['exe', 'lib']:
        exit("type error in cmake.config")
    cmake = "cmake_minimum_required(VERSION {0})".format(js["cmake_version"]) + enter_str
    if "gcc" in js and js["gcc"] != "":
        cmake += '''SET(CMAKE_C_COMPILER "{0}")'''.format(js["gcc"]) + enter_str
    if "g++" in js and js["g++"] != "":
        cmake += '''SET(CMAKE_CXX_COMPILER "{0}")'''.format(js["g++"]) + enter_str
    cmake += "project({0})".format(project_name) + enter_str
    if include_path != "":
        cmake += "INCLUDE_DIRECTORIES({0})".format(include_path) + enter_str
    if lib_path != "":
        cmake += "LINK_DIRECTORIES({0})".format(lib_path) + enter_str
    if output_path != "":
        if exe_type == "exe":
            cmake += "SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/"

        elif exe_type == "lib":
            cmake += "SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/"
        cmake += "{0})".format(output_path) + enter_str
    cmake += '''SET(CMAKE_BUILD_TYPE "Release")''' + enter_str
    cmake += "ADD_DEFINITIONS({0})".format(js['options']) + enter_str

    new_path_src = walk_and_modify_cpp(dir_name)
    if exe_type == "exe":
        cmake += "ADD_EXECUTABLE({0} ".format(project_name)
    elif exe_type == "lib":
        cmake += "ADD_LIBRARY({0} ".format(project_name) + enter_str
    cmake += new_path_src
    cmake += ")" + enter_str
    if lib != "":
        cmake += "TARGET_LINK_LIBRARIES({0} {1})".format(project_name, lib) + enter_str
    write_file_context(dir_name, "CMakeLists.txt", cmake)
    print(cmake)


if __name__ == "__main__":
    main()

cmake.config (只贴了loginapp的)

{
  "cmake_version":"2.8.12.2",
  "gcc":"",
  "g++":"",
  "type": "exe",
  "project":"loginapp",
  "include_path":"./ ../ ./mysql /usr/include/mysql/",
  "lib_path": "../lib",
  "lib": "engine event event_pthreads protobuf tcmalloc_minimal pthread",
  "output_path": "../bin",
  "options":"-DNDEBUG -O3 -Wall -c -fmessage-length=0 -std=c++11 -fpermissive -lpthread"
}
cd demo5
python cmakelist.py engine
mkdir build
cd build
mkdir engine
cd engine
cmake ../../engine
make

然后执行python cmakelist.py engine(文件夹名)来生成CMakeLists.txt

对于上面方式,就是在cmake外层又包了一层python,这样就像套娃中的套娃。。。

2.使用CMake函数

直到看到CMake可以通过函数找到源文件,果断放弃python的方式

├── AutoCollect.cmake
├── CMakeLists.txt
├── engine
│ ├── CMakeLists.txt
│ ├── config.h
│ ├── gameapp
│ │ ├── gameapp.cpp
│ │ └── gameapp.h
│ ├── json
│ │ ├── allocator.h
│ │ ├── assertions.h
.....
│ ├── timer
....
│ │ └── timerutil.h
│ └── util
│ ├── autolock.cpp
....
│ └── shared.h
├── loginapp
│ ├── CMakeLists.txt
│ ├── loginapp.cpp
│ ├── loginapp.h
│ ├── main.cpp
│ └── module
│ ├── loginhelper.cpp
│ └── loginhelper.h
└── testclient
├── main.cpp
├── testclient.cpp
└── testclient.h

AutoCollect.cmake

function(CollectSourceFiles current_dir variable)
  list(FIND ARGN "${current_dir}" IS_EXCLUDED)
  if(IS_EXCLUDED EQUAL -1)
    file(GLOB COLLECTED_SOURCES
      ${current_dir}/*.c
      ${current_dir}/*.cc
      ${current_dir}/*.cpp
      ${current_dir}/*.inl
      ${current_dir}/*.def
      ${current_dir}/*.h
      ${current_dir}/*.hh
      ${current_dir}/*.hpp)
    list(APPEND ${variable} ${COLLECTED_SOURCES})

    file(GLOB SUB_DIRECTORIES ${current_dir}/*)
    foreach(SUB_DIRECTORY ${SUB_DIRECTORIES})
      if (IS_DIRECTORY ${SUB_DIRECTORY} AND NOT(${SUB_DIRECTORY} MATCHES ".svn"))
        CollectSourceFiles("${SUB_DIRECTORY}" "${variable}" "${ARGN}")
      endif()
    endforeach()
    set(${variable} ${${variable}} PARENT_SCOPE)
  endif()
endfunction()

主目录下的CMakaLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

PROJECT(gameproject)

SET(APP_INSTALL_PATH "${PROJECT_SOURCE_DIR}/../bin")

# set macro-directory
LIST(APPEND CMAKE_MODULE_PATH
  "${PROJECT_SOURCE_DIR}/")  
  
INCLUDE(AutoCollect)

INCLUDE_DIRECTORIES(
  ${PROJECT_SOURCE_DIR}/
  /usr/include/mysql/
)

LINK_DIRECTORIES(
  ${PROJECT_SOURCE_DIR}/../../lib/
)

ADD_SUBDIRECTORY(engine)
ADD_SUBDIRECTORY(loginapp)

engine目录下的CMakeLists.txt

CollectSourceFiles(
        ${CMAKE_CURRENT_SOURCE_DIR}
        SRC_LIST)

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -fmessage-length=0 -std=c++11")

add_definitions(-DNDEBUG)

ADD_LIBRARY(engine STATIC ${SRC_LIST})

loginapp目录下的CMakeLists.txt

CollectSourceFiles(
        ${CMAKE_CURRENT_SOURCE_DIR}
        SRC_LIST)

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -fmessage-length=0 -std=c++11")

add_definitions(-DNDEBUG)

ADD_EXECUTABLE(loginapp ${SRC_LIST})

TARGET_LINK_LIBRARIES(loginapp engine event event_pthreads pthread)

INSTALL(TARGETS loginapp DESTINATION ${APP_INSTALL_PATH})

testclient 是用测试的,就没写了。。。


相关推荐