CMake 簡要教程
在 C/C++ 領(lǐng)域潦俺, 一直很困擾我的是它沒有一個令我覺得很 “用戶友好” 的 “編譯/構(gòu)建” 系統(tǒng)(很可能是有,只是我不知道而已)渡紫。 一般情況下横媚,如果只是寫一個 demo,我就直接命令行 gcc
編譯了一罩;如果是小工程杨幼,就手寫一個 Makefile
編譯了。
記得剛接觸 OpenCV
的時候聂渊,想自己編譯一份源碼差购,那真是的一場 “戰(zhàn)爭”。各種編譯汉嗽,各種報錯欲逃。從那時候起,就想著要好好學(xué)習一下 CMake
了饼暑,但是每次都是看一下就忘稳析,看一下就忘。
最近弓叛,因為迫不得已要聯(lián)調(diào)一些 Java
和 C++
的代碼彰居,不能連怎么編譯人家的代碼都不會,并且想要在 Java
里調(diào)用 C++
還涉及到庫的連接等一堆問題撰筷。所以陈惰,決定好好學(xué)習一下 CMake
。
在往下面寫之前先強調(diào)幾個事情:
- 不是迫不得已毕籽,不要搞什么
JNI
(就是別沒事要在 Java 里調(diào) C++抬闯,反之亦然); - 不是迫不得已关筒,不要寫 C++ 了 (我是 C++ 勸退黨)溶握;
- 不是迫不得己,也不要學(xué)什么
CMake
平委;
如果你還在看奈虾,那肯定就是迫不得已了,那沒招了廉赔,好好往下看吧。本文將會按照以下幾個步驟來講解怎么使用 CMake
:
- 最最最簡單的一個
CMakeLists.txt
怎么寫以及怎么使用它匾鸥; - 如何使用
CMake
進行工程管理和構(gòu)建 (每個子目錄都要寫一個CMakeLists.txt
)蜡塌; - 如何構(gòu)建靜態(tài)庫和動態(tài)庫;
- 如何使用別人構(gòu)建好的庫勿负;
最后馏艾,本文默認讀者具有一定的命令行使用經(jīng)驗劳曹。
最簡單的例子
打開命令行:
// cd 到一處你準備放置本文示例的目錄下
mkdir cmake_practice && cd cmake_practice
mkdir simple_example && cd simple_example
// 創(chuàng)建兩個文件
touch main.c
touch CMakeLists.txt
// 創(chuàng)建一個目標文件夾 (所有的構(gòu)建結(jié)果和中間文件會在這里面, 不污染源嗎目錄)
// 這是 cmake 推薦的 build out-of-source, 全文都采取這種方式
mkdir build
往建好的兩個文件里填入如下內(nèi)容:
// main.c
#include<stdio.h>
int main(int argc, char *argv[]) {
printf("Hello world (build from cmake)");
return 0;
}
// CMakeLists.txt
cmake_minimum_required (VERSION 3.8)
project (HelloWorld)
add_executable (HelloWorld main.c)
上面這一小段一共有三個命令,逐一解釋一下:
-
cmake_minimum_required
: 這個顧名思義了琅摩,最小的 cmake 版本要求铁孵; -
project
: 給項目起一個名字,這個命令會隱式的定義兩個變量房资,PROJECT_SOURCE_DIR
和PROJECT_BINARY_DIR
蜕劝; -
add_executable
: 定義可執(zhí)行文件的名字 (第一個參數(shù)) 和 用于編譯得到這個可執(zhí)行文件的依賴文件 (第二個參數(shù));
再回到命令行轰异,cd
到剛才建好的 build
文件夾下面:
cmake .. && make
如此岖沛,我們就完成了使用 CMake
構(gòu)建一個可執(zhí)行文件的最簡單例子。在實際生活中搭独,如果只是需要編譯一個文件我們不需要使用如此復(fù)雜的步驟婴削,直接命令行 gcc
即可,這里僅僅是為了演示 CMake
的使用牙肝。
工程化
一般來說唉俗,當我們的文件比較多的時候,我們會根據(jù)某種邏輯配椭,把不同的源碼文件放在不同的子文件夾中進行管理虫溜。要到達這個目標,我們需要在每一個子文件夾下寫一個 CMakeLists.txt
這個文件負責告訴 CMake
如何處理這個文件夾下的文件颂郎。
繼續(xù)拿上面的例子為例吼渡,但是這一次,我需要把所有的源碼文件放大一個 src
目錄下乓序。
打開命令行:
// 假設(shè)我們在上面設(shè)置好的 "cmake_practice" 目錄下
mkdir work_with_sub_dir && cd work_with_sub_dir
mkdir src && cd src
touch main.c
touch CMakeLists.txt
cd ..
mkdir build
touch CMakeLists.txt
然后分別在新建的三個文件中填入如下內(nèi)容:
// work_with_sub_dir 目錄下的 CMakeLists.txt
cmake_minimum_required (VERSION 3.8)
project (WorkWithSubDir)
add_subdirectory(src bin)
這里有一個新的命令:
add_subdirectory
: 這個命令的意思是寺酪,在構(gòu)建的目標文件夾下,用 bin
作為目錄明替換掉原來的 src
// src 目錄下的 CMakeLists.txt
cmake_minimum_required (VERSION 3.8)
add_executable (WorkWithSubDir main.c)
// src 目錄下的 main.c
#include<stdio.h>
int main() {
printf("Hello world from main.c in src\n");
return 0;
}
回到命令行切換到 build
目錄下:
cmake .. && make
bin/WorkWithSubDir
應(yīng)該可以看到 Hello world from main.c in src
在控制臺輸出替劈。需要在多層目錄下構(gòu)建時寄雀,重點就在于為每一個子目錄寫一個 CMakeLists.txt
文件,告訴 cmake 如何處理該文件下的文件陨献。
構(gòu)建庫以及庫安裝
首先新建一個目錄盒犹,按照如下的結(jié)構(gòu)新建文件:
分別填入內(nèi)容:
// CMakeLists.txt
# define minimum version of cmake
cmake_minimum_required (VERSION 3.8)
PROJECT(create_lib)
ADD_SUBDIRECTORY(lib)
// hello.c
#include "hello.h"
void HelloFunc() {
printf("Hello World\n");
}
// hello.h
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
前面的內(nèi)容都很尋常,唯一比較復(fù)雜的是 lib/CMakeLists.txt
// lib/CMakeLists.txt
# define minimum version of cmake
cmake_minimum_required (VERSION 3.8)
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
INSTALL(
TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
INSTALL(FILES hello.h DESTINATION include/hello)
一個一個地看看這些命令:
- SET(LIBHELLO_SRC hello.c), 設(shè)置一個變量
- ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC}), 編譯一個動態(tài)庫
- ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC}), 編譯一個靜態(tài)庫眨业,第一個參數(shù)是名字急膀,由于不能重名所以這樣命名
- SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello”), 因為不管動態(tài)還是靜態(tài)庫 我們期望他們的名字應(yīng)該是一樣的,這里把名字設(shè)置成一樣都是 “hello”
- INSTALL(FILES hello.h DESTINATION include/hello) 安裝一系列的頭文件
- INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib), 安裝庫文件
編譯完成了龄捡,下面關(guān)心一下怎么安裝卓嫂。
安裝的主要作用是,把一些需要的頭文件和編譯好的庫文件放到系統(tǒng)特定的目錄中 (比如 /usr/local/lib, /usr/local/include 等)聘殖。想到達到這一步需要知道兩個事情:
- cmake 的
-DCMAKE_INSTALL_PREFIX
標志 : 這個標志主要用于制定安裝的路徑前綴晨雳; - cmake 的
INSTALL
命令 : 這個命令用來告訴 cmake 怎么安裝各種不同的文件或者庫 (怎么使用看上面的示例)行瑞;
繼續(xù)用上面的例子,命令行 cd
到 build
文件下:
cmake DCMAKE_INSTALL_PREFIX=../install ..
cmake .. && make && make install
因為我不想污染系統(tǒng)環(huán)境餐禁,所以就安裝在了當前的工程的根目錄下血久,執(zhí)行完上面的命令,應(yīng)該能看到如下的目錄結(jié)構(gòu):
使用編譯好的庫
終于來到了最后一步帮非,我們繼續(xù)上面的例子氧吐,我們已經(jīng)編譯了一個靜態(tài)一個動態(tài)庫并且將相關(guān)的頭文件安裝在了一些特定的目錄里面。下面我們來看看如何調(diào)用已經(jīng)編譯好的庫喜鼓。
建立如下所示的文件:
填入如下的內(nèi)容:
// CMakeLists.txt
# define minimum version of cmake
cmake_minimum_required (VERSION 3.8)
PROJECT(use_lib)
ADD_SUBDIRECTORY(src bin)
// src/CMakeLists.txt
# define minimum version of cmake
cmake_minimum_required (VERSION 3.8)
ADD_EXECUTABLE(main main.c)
INCLUDE_DIRECTORIES(<where you install the header files>/create_lib/install/include/hello)
FIND_LIBRARY(HELLO_LIB hello <where you install your libraries file>/create_lib/install/lib)
TARGET_LINK_LIBRARIES(main ${HELLO_LIB})
#include <hello.h>
int main() {
HelloFunc();
return 0;
}
這里重點是兩個命令:
-
INCLUDE_DIRECTORIES
: 如果你的頭文件不是安裝在系統(tǒng)必須搜尋的路徑副砍,需要這個命令告訴 cmake 去找到你的頭文件 -
FIND_LIBRARY
: 如果你的庫文件不是安裝在系統(tǒng)必須搜尋的路徑,需要這個命令告訴 cmake 去找到你的庫文件 -
TARGET_LINK_LIBRARIES
: 將找到的庫和可執(zhí)行文件 (或者其他的庫) 連接到一起
最后 cd build && cmake .. && make
應(yīng)該可以看到調(diào)用了之前寫的 HelloFunc()
函數(shù)庄岖。