cMake の実践的なチュートリアル、簡単に学べる cMake

        cMakeを学習していく過程で、シンプルで分かりやすく書かれた記事を見つけたのでメモしておきました。

        CMake はオープン ソースのクロスプラットフォーム ビルド ツールであり、簡単な構成ファイルを作成することでローカルの Makefile を生成できます。この構成ファイルはオペレーティング プラットフォームやコンパイラから独立しているため、自分で Makefile を作成する必要はありません。および設定ファイルを変更せずに他のプラットフォームで直接使用できるため、非常に便利です。

この記事では主に、CMake を使用して Linux 上でプログラムをコンパイルする方法について説明します。

1. CMake をインストールする
この記事では ubuntu18.04 を使用します。cmake をインストールするには、次のコマンドを使用します。インストールが完了
したら
、ターミナルで cmake -version と入力して cmake のバージョンを表示します。

ここに画像の説明を書きます

このようにしてcmakeがインストールされます。

2. 簡単な例
まず、最も単純なコードから始めて、cmake がどのように動作するかを体験してみましょう。main.c を次のように記述します。

#include <stdio.h>

int main(void)
{
	printf("Hello World\n");

	return 0;
}

次に、main.c と同じディレクトリに CMakeLists.txt を書き込みます。内容は次のとおりです。

cmake_minimum_required (VERSION 2.8)

project (demo)

add_executable(main main.c)

最初の行は、cmake の最小バージョン要件が 2.8 であり、3.10.2 をインストールしたことを意味します。2 行目はプロジェクト情報を示します。つまり、プロジェクト名はdemoです。3 行目はより重要で、最終的な elf を示します。ファイル名は main で、使用されるソース ファイルは main.c です。
ターミナルで main.c があるディレクトリに移動し、次のコマンドを入力して cmake を実行します。cmake は次の情報を出力します

ここに画像の説明を書きます

 ディレクトリ内のファイルをもう一度見てください

ここに画像の説明を書きます

 Makefile が正常に生成され、cmake の実行時にいくつかのファイルが自動的に生成されたことがわかります。
次にターミナルに「make」と入力してEnterを押します

ここに画像の説明を書きます

 cmakeを実行して生成されたMakefileでは進行状況を色で表示できることがわかります。ディレクトリ内のファイルを見てください

ここに画像の説明を書きます

 必要な elf ファイル main も正常に生成されたことがわかり、main を実行します。

ここに画像の説明を書きます

 無事に実行されました!

PS: main を再生成したい場合は、make clean と入力してメインの elf ファイルを削除します。

3. 同じディレクトリ内の複数のソース ファイル
次に、少し複雑な例を入力します。同じディレクトリ内に複数のソース ファイルがあります。
前のディレクトリに 2 つのファイル、testFunc.c と testFunc.h を追加します。追加後の全体的なファイル構造は次のようになります。

ここに画像の説明を書きます

 testFunc.cの内容は以下の通りです

/*
** testFunc.c
*/

#include <stdio.h>
#include "testFunc.h"

void func(int data)
{
	printf("data is %d\n", data);
}

testFunc.hの内容は以下の通りです。

/*
** testFunc.h
*/

#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_

void func(int data);

#endif


main.c を変更し、testFunc.h で宣言された関数 func() を呼び出します。

#include <stdio.h>

#include "testFunc.h"

int main(void)
{
	func(100);

	return 0;
}


CMakeLists.txtを変更し、add_executableのパラメータにtestFunc.cを追加します。

cmake_minimum_required (VERSION 2.8)

project (demo)

add_executable(main main.c testFunc.c)


次に cmake を再実行して Makefile を生成し、make を実行します。

ここに画像の説明を書きます

 次に、再生成されたelfファイルmainを実行します。

ここに画像の説明を書きます

 無事に実行されました!

同様に、同じディレクトリに複数のソース ファイルがある場合は、すべてのソース ファイルを add_executable に追加するだけです。ただし、ソース ファイルが 100 個もある場合、これをもう一度行うのは少し難しく、cmake の優位性を反映できません。cmake には、指定したディレクトリにあるすべてのソース ファイルを変数に格納するコマンドが用意されています。このコマンドは aux_source_directory です(ディレクトリ変数)。
最初のパラメータ dir は指定されたディレクトリで、2 番目のパラメータ var はソース ファイルのリストを格納するために使用される変数です。

main.c が配置されているディレクトリに、さらに 2 つのファイル、testFunc1.c と testFunc1.h を追加します。追加後の全体的なファイル構造は次のようになります。

ここに画像の説明を書きます

 testFunc1.cは以下の通りです。

/*
** testFunc1.c
*/

#include <stdio.h>
#include "testFunc1.h"

void func1(int data)
{
	printf("data is %d\n", data);
}


testFunc1.hは以下の通りです。

/*
** testFunc1.h
*/

#ifndef _TEST_FUNC1_H_
#define _TEST_FUNC1_H_

void func1(int data);

#endif


次に、main.c を変更し、testFunc1.h で宣言された関数 func1() を呼び出します。

#include <stdio.h>

#include "testFunc.h"
#include "testFunc1.h"

int main(void)
{
	func(100);
	func1(200);

	return 0;
}


CMakeLists.txtを変更し、

cmake_minimum_required (VERSION 2.8)

project (demo)

aux_source_directory(. SRC_LIST)

add_executable(main ${SRC_LIST})


aux_source_directory を使用して、現在のディレクトリにあるソース ファイルの格納リストを変数 SRC_LIST に格納し、add_executable で SRC_LIST を呼び出します (変数の呼び出し方法に注意してください)。
cmakeを実行して再度makeし、mainを実行します。

ここに画像の説明を書きます

 操作が成功したことがわかります。

aux_source_directory() には欠点もあります。指定されたディレクトリ内のすべてのソース ファイルが追加され、必要のないファイルも追加される可能性があります。このとき、set コマンドを使用して必要なソース ファイルを格納する変数を作成できます。次のように、

cmake_minimum_required (VERSION 2.8)

project (demo)

set( SRC_LIST
	 ./main.c
	 ./testFunc1.c
	 ./testFunc.c)

add_executable(main ${SRC_LIST})


4. 複数のソースファイルを別のディレクトリに配置する
一般に、プログラムファイルが多い場合は、カテゴリーごとに管理し、コードを機能ごとに別のディレクトリに配置して見つけやすいようにします。では、この場合 CMakeLists.txt はどのように書くのでしょうか?
前のソース ファイルを整理しましょう (test_func と test_func1 という 2 つの新しいディレクトリを作成します) 終了後の全体的なファイル構造は次のようになります。

ここに画像の説明を書きます

 先ほどの testFunc.c と testFunc.h を test_func ディレクトリに配置し、testFunc1.c と testFunc1.h を test_func1 ディレクトリに配置します。

このうち、CMakeLists.txtとmain.cは同じディレクトリにあり、内容は以下のように変更されます

cmake_minimum_required (VERSION 2.8)

project (demo)

include_directories (test_func test_func1)

aux_source_directory (test_func SRC_LIST)
aux_source_directory (test_func1 SRC_LIST1)

add_executable (main main.c ${SRC_LIST} ${SRC_LIST1})


新しいコマンド include_directories が登場します。このコマンドは、指定された複数のヘッダー ファイルの検索パスをプロジェクトに追加するために使用されます。パスはスペースで区切られます。
main.c には testFunc.h と testFunc1.h が含まれているため、このコマンドでヘッダー ファイルの場所を指定しないとコンパイルできません。もちろん、次のように include を使用して main.c 内のパスを指定することもできます。

#include "test_func/testFunc.h"
#include "test_func1/testFunc1.h"

ただ書き方が良くないだけです。
また、aux_source_directory を 2 回使用しましたが、これはソースファイルが 2 つのディレクトリに分散されているため、2 回追加します。

5. 正式な組織構造
形式的には、一般にソース ファイルは src ディレクトリに配置され、ヘッダ ファイルはインクルード ファイルに配置され、生成されたオブジェクト ファイルは build ディレクトリに配置され、最終的に出力される elf ファイルは次のようになります。全体の構造がより明確になるように、「bin ディレクトリに移動」ディレクトリに配置されます。先ほどのファイルをもう一度整理してみましょう

ここに画像の説明を書きます

 一番外側のディレクトリに新しい CMakeLists.txt を作成します。内容は次のとおりです。

cmake_minimum_required (VERSION 2.8)

project (demo)

add_subdirectory (src)

新しいコマンド add_subdirectory() がここに表示されます。このコマンドは、ソース ファイルを保存するためのサブディレクトリを現在のプロジェクトに追加し、中間バイナリとターゲット バイナリの保存場所を指定できます。具体的な使用方法は Baidu です。
ここでは、ソース ファイルは src ディレクトリに保存されています。cmake が実行されると、src ディレクトリに入り、src ディレクトリ内の CMakeLists.txt を見つけます。そのため、CMakeLists.txt も src ディレクトリに作成され、その内容は次のようになります。次のように

aux_source_directory (. SRC_LIST)

include_directories (../include)

add_executable (main ${SRC_LIST})

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

ここでは変数を定義するための新しいコマンドセットがあり、EXECUTABLE_OUT_PATH とPROJECT_SOURCE_DIRは CMake に付属する定義済み変数で、それぞれの意味は以下の通りです。

EXECUTABLE_OUTPUT_PATH : 対象のバイナリ実行ファイルの格納場所
PROJECT_SOURCE_DIR : プロジェクトのルートディレクトリ
したがって、ここで設定するということは、elf ファイルの場所をプロジェクトのルートディレクトリ下の bin ディレクトリに設定することを意味します。(cmake には多くの事前定義された変数があります。詳細についてはオンラインで検索できます)

上記 2 つの CMakeLists.txt を追加した後の全体的なファイル構造は次のようになります。

ここに画像の説明を書きます

 cmake を実行しましょう。ただし、今回は最初にビルド ディレクトリに切り替えて、次のコマンド
cmakeを入力します。
ビルド ディレクトリに Makefile が生成され、ビルド ディレクトリで make を実行します。

ここに画像の説明を書きます

 「OK」を実行し、bin ディレクトリに切り替えて、main が生成されていることを確認し、テストを実行します。

ここに画像の説明を書きます

 テストOK!

ここでは、cmake が build ディレクトリで実行される理由について説明します。前の例からわかるように、これを行わないと、cmake の実行時に生成される付随ファイルがソース コード ファイルと混合され、プログラムのディレクトリ構造が汚染され、ビルド ディレクトリで cmake が実行されます。生成された添付ファイルはビルド ディレクトリにのみ残りますが、これらのファイルが不要な場合はビルド ディレクトリを直接クリアできるため、非常に便利です。

別の書き方:
前のプロジェクトでは 2 つの CMakeLists.txt を使用し、一番外側の CMakeLists.txt は全体の状況を制御するために使用され、add_subdirectory を使用して他のディレクトリ内の CMakeLists.txt の操作を制御します。

上記の例では、CMakeLists.txt を 1 つだけ使用し、最も外側の CMakeLists.txt の内容を次のように変更することもできます。

cmake_minimum_required (VERSION 2.8)

project (demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

aux_source_directory (src SRC_LIST)

include_directories (include)

add_executable (main ${SRC_LIST})


同時に、srcディレクトリ内のCMakeLists.txtを削除します。

6. 動的ライブラリと静的ライブラリのコンパイル制御 動的
ライブラリと静的ライブラリをコンパイルするだけで、他のプログラムがそれらを使用するのを待つ必要がある場合があります。この場合の cmake の使用方法を見てみましょう。まず、testFunc.h と TestFunc.c だけを残して、次のようにファイルを再編成します。

ここに画像の説明を挿入

 build ディレクトリで cmake を実行し、生成されたライブラリ ファイルを lib ディレクトリに保存します。
CMakeLists.txtの内容は以下の通りです。

cmake_minimum_required (VERSION 3.5)

project (demo)

set (SRC_LIST ${PROJECT_SOURCE_DIR}/testFunc/testFunc.c)

add_library (testFunc_shared SHARED ${SRC_LIST})
add_library (testFunc_static STATIC ${SRC_LIST})

set_target_properties (testFunc_shared PROPERTIES OUTPUT_NAME "testFunc")
set_target_properties (testFunc_static PROPERTIES OUTPUT_NAME "testFunc")

set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

ここにも新しいコマンドと事前定義された変数があります。

add_library: 動的ライブラリまたは静的ライブラリを生成します (最初のパラメータはライブラリの名前を指定します。2 番目のパラメータはそれが動的か静的かを決定し、そうでない場合はデフォルトで静的です。3 番目のパラメータは生成されたライブラリのソース ファイルを指定します) library) set_target_properties: 最終
世代を設定します。ライブラリの名前と、ライブラリのバージョン番号の設定などのその他の機能を設定します。
LIBRARY_OUTPUT_PATH: ライブラリ ファイルのデフォルトの出力パス。ここでは、ライブラリ内の lib ディレクトリに設定されます。プロジェクト ディレクトリ
、ビルド ディレクトリに入って cmake を実行します。成功したら make を実行します。

ここに画像の説明を書きます

 cd で lib ディレクトリに移動して確認すると、ダイナミック ライブラリとスタティック ライブラリが正常に生成されていることがわかります。

ここに画像の説明を書きます

 PS: Set_target_properties を使用してライブラリの出力名を再定義しました。set_target_properties が使用されていない場合、ライブラリの名前は add_library で定義された名前になりますが、add_library を使用してライブラリ名 (最初のパラメータ) を 2 回指定すると、行、名前 同じにすることはできませんが、set_target_properties は名前を同じに設定できますが、最終的に生成されるライブラリ ファイルのサフィックスは異なります (1 つは .so、もう 1 つは .a)。良い。

7. ライブラリをリンクする ライブラリ
を生成したので、リンクをテストしましょう。プロジェクト ディレクトリを再構築し、前のセクションで生成されたライブラリをコピーし、プロジェクト ディレクトリの下に新しい src ディレクトリと bin ディレクトリを作成し、src ディレクトリの下に main.c を追加します。全体の構造は次のようになります。

ここに画像の説明を挿入

main.cの内容は以下の通りです

#include <stdio.h>

#include "testFunc.h"

int main(void)
{
    func(100);
    
    return 0;
}

プロジェクトディレクトリ内のCMakeLists.txtの内容は以下の通りです

cmake_minimum_required (VERSION 3.5)

project (demo)


set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

set (SRC_LIST ${PROJECT_SOURCE_DIR}/src/main.c)

# find testFunc.h
include_directories (${PROJECT_SOURCE_DIR}/testFunc/inc)

find_library(TESTFUNC_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)

add_executable (main ${SRC_LIST})

target_link_libraries (main ${TESTFUNC_LIB})

ここに 2 つの新しいコマンドが表示されます

find_library : 指定されたディレクトリで指定されたライブラリを検索し、ライブラリの絶対パスを変数に格納します。最初のパラメータは変数名、2 番目のパラメータはライブラリ名、3 番目のパラメータは HINTS、4 番目のパラメータはその他の使用方法については、cmake ドキュメントを参照してください。
target_link_libraries : ターゲット ファイルとライブラリ ファイルをリンクします
。 find_library を使用する利点は、cmake .. の実行時にライブラリが存在するかどうかをチェックするため、リンクを待たずにエラーを事前に発見できることです。 。

cd でビルド ディレクトリに移動し、cmake .. && make を実行して、最後に bin ディレクトリに入って確認します。main が生成されていることがわかったので、それを実行します。

ここに画像の説明を書きます

 無事に実行されました!

ps: lib ディレクトリには testFunc の静的ライブラリと動的ライブラリがあります。 find_library(TESTFUNC_LIB testFunc ... デフォルトでは動的ライブラリを検索します。動的ライブラリと静的ライブラリのどちらを使用するかを直接指定したい場合は、 find_library(TESTFUNC_LIB libtestFunc.so ... または find_library(TESTFUNC_LIB libtestFunc.a ...) と書くことができます。

ps: elf ファイルでどのライブラリが使用されているかを確認するには、 readelf -d ./xx を使用して確認できます。

このセクションの前のチュートリアルでは、ライブラリの検索方法として link_directories を使用していましたが、実行時に問題が発生したと多くの読者が報告しました。公式ドキュメントを確認したところ、これを使用することは推奨されていないことがわかりました。find_library または find_package を使用することをお勧めします。

ここに画像の説明を挿入

 8. コンパイル オプションの追加
プログラムのコンパイル時に、-Wall、-std=c++11 などのコンパイル オプションを追加したい場合は、add_compile_options を使用して操作できます。
これはデモ用の簡単なプログラムです。main.cpp は次のとおりです。

#include <iostream>

int main(void)
{
    auto data = 100;
    std::cout << "data: " << data << "\n";
    return 0;
}

CMakeLists.txtの内容は以下の通りです

cmake_minimum_required (VERSION 2.8)

project (demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_compile_options(-std=c++11 -Wall) 

add_executable(main main.cpp)

全体的なディレクトリ構造は次のとおりです

ここに画像の説明を挿入

 次に cd でビルド ディレクトリに移動し、cmake .. && make コマンドを実行すると、bin ディレクトリにメインの elf ファイルを取得できます。

9、制御オプションを追加する コードを
コンパイルするときに、指定した一部のソース コードのみをコンパイルしたい場合があります。cmake のオプション コマンドを使用できます。主に 2 種類の状況が発生します。

当初は複数の bin またはライブラリ ファイルを生成したいと思っていましたが、現在は指定された bin またはライブラリ ファイルのみを生成したいと考えています。同じ
bin ファイルについて、コードの一部のみをコンパイルしたいです (マクロを使用して制御します)。
最初のケースは次のこと
を前提としています。現在のプロジェクトは 2 つの bin ファイル、main1 と main2 を生成します。全体の構造は次のようになります。

ここに画像の説明を挿入

 外側の CMakeLists.txt の内容は次のとおりです

cmake_minimum_required(VERSION 3.5)

project(demo)

option(MYDEBUG "enable debug compilation" OFF)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_subdirectory(src)


ここではオプション コマンドが使用されています。最初のパラメータはオプションの名前、2 番目のパラメータはオプションの内容を説明するために使用される文字列、3 番目のパラメータはオプションの値 (ON または OFF) です。書き込み禁止もできます。書き込み禁止はデフォルトの OFF です。

次に、次のように src ディレクトリに CMakeLists.txt を書き込みます。

cmake_minimum_required (VERSION 3.5)

add_executable(main1 main1.c)

if (MYDEBUG)
    add_executable(main2 main2.c)
else()
    message(STATUS "Currently is not in debug mode")    
endif()


なお、ここでの if-else は、オプションに従って main2.c をコンパイルするかどうかを決定するために使用されます

// main1.c
#include <stdio.h>

int main(void)
{
    printf("hello, this main1\n");
    
    return 0;
}
// main2.c
#include <stdio.h>

int main(void)
{
    printf("hello, this main2\n");
    
    return 0;
}

次に cd でビルド ディレクトリに移動し、cmake .. && make と入力して main1 のみをコンパイルします。main2 をコンパイルする場合は、MYDEBUG を ON に設定し、再度 cmake .. && make と入力して再コンパイルします。

MYDEBUGを変更するたびにCMakeLists.txtを修正する必要があり、少々面倒ですが、実際にはcmakeのコマンドラインから操作できます。例えばMYDEBUGをOFFに設定したい場合は、まずcdします。ビルド ディレクトリに移動し、cmake .. -DMYDEBUG= ON と入力して、main1 と main2 を (bin ディレクトリ内で) コンパイルできるようにします。

ここに画像の説明を挿入

ケース 2
次のような内容の main.c があるとします。

#include <stdio.h>

int main(void)
{
#ifdef WWW1
    printf("hello world1\n");
#endif    

#ifdef WWW2     
    printf("hello world2\n");
#endif

    return 0;
}


マクロを定義することで、印刷される情報を制御できます。CMakeLists.txt の内容は次のとおりです。

cmake_minimum_required(VERSION 3.5)

project(demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

option(WWW1 "print one message" OFF)
option(WWW2 "print another message" OFF)

if (WWW1)
    add_definitions(-DWWW1)
endif()

if (WWW2)
    add_definitions(-DWWW2)
endif()

add_executable(main main.c)


ここでは、オプションの名前は main.c のマクロ名と同じままになっており、より直感的ですが、別の名前を選択することもできます。add_settings() と連携することで、単一の bin ファイルの印刷出力を制御できます。

プロジェクト全体の構成は次のとおりです

ここに画像の説明を挿入

 cd でビルド ディレクトリに移動し、cmake .. && make を実行し、bin ディレクトリで ./main を実行します。印刷結果が空であることが確認できます。次に、以下の手順に従って実行し、印刷効果を確認します

cmake .. -DWWW1=ON -DWWW2=OFF && make
cmake .. -DWWW1=OFF -DWWW2=ON && make
cmake .. -DWWW1=ON -DWWW2=ON && make
ここには注意すべき小さな落とし穴があります。 2 つのオプションは A と B と呼ばれます。最初に cmake を呼び出して A を設定し、次に cmake を呼び出して B を設定します。cmake の最後の実行中に生成されたキャッシュ ファイルが削除されていない場合、A は設定されていませんが、今回は、A がデフォルトで使用され、最後のオプション値です。

したがって、オプションが変更された場合は、cmake が最後に実行されたときに生成されたキャッシュ ファイルを削除するか、すべてのオプションにその値を明示的に指定します。

10 のまとめ
以上は、CMake を独学で学習したちょっとした学習記録です。簡単な例を通して、皆さんも CMake を始めましょう。学習中、ネチズンのブログもたくさん読みました。CMake についてはまだ多くのナレッジポイントがあり、具体的な詳細はオンラインで検索できます。つまり、CMake を使用すると、複雑な Makefile を作成する手間が省け、クロスプラットフォームであるため、学ぶ価値のある非常に強力なツールです。
———————————————
著作権に関する声明: この記事は CSDN ブロガー「Love is Perseverance」のオリジナル記事であり、CC 4.0 BY-SA 著作権契約に従っており、オリジナルのソースリンクを添付してください。転載とこの声明に対して。
元のリンク: https://blog.csdn.net/whahu1989/article/details/82078563

おすすめ

転載: blog.csdn.net/weixin_49071468/article/details/130364101