C++のためのConan2とCMake(Linux)

# はじめに
　本記事はライブラリを簡単に使用するためConan2の使い方を紹介します。また、C++のコンパイルを簡単化するため、Conan2を絡めてCMakeの使い方を紹介します。
 
# 環境
・2024/02/26
・GitHub Codespaces
・CMake v3.16.3
・Conan v2.1.0

# Conan2とは
　Conan2はC/C++のライブラリ管理ツールです。このツールを使用するとプロジェクトに使用するさまざまなライブラリをインストールし、管理することができます。

# CMakeとは
　CMakeはクロスプラットフォームのビルド自動化ツールです。CMkaeはプロジェクトのソースコードを解析し、適切なビルド手順を生成するため、ビルド手順の記述が簡単であり、プラットフォーム間の移植性が高いという特徴を持ちます。

 # インストール方法
## Conan2のインストール
　Conan2をインストールするためのコマンドは以下の通りです。

```
sudo pip install conan
```

## CMakeのインストール
　Ubuntuを例としたCMakeインストールのためのコマンドは以下の通りです。APTはOSにより変更してください。

 ```
sudo apt update
sudo apt install cmake
 ```

# デモンストレーションの準備
　本記事ではConan2とCMakeの使い方を説明するため、デモンストレーションのコードを用意します。以下がそのコードです。このコードは[GitHubのディスカッション](https://gist.github.com/halloweeks/8bb0a745229b4674aaacffc7a23e5956)から引用しています。
　コードはCMakeによりビルドします。zlibはConan2によりインストールします。

 ```cpp:main.cpp
#include <iostream>
#include <string.h>
#include <stdexcept>
#include <iomanip>
#include <sstream>
#include <zlib.h>

class myzlib {
	public:
	std::string compress(const std::string&, int);
	std::string decompress(const std::string&);
};

/** Compress a STL string using zlib with given compression level and return* the binary data. */
std::string myzlib::compress(const std::string& str, int compressionlevel = Z_BEST_COMPRESSION) {
    z_stream zs;                        // z_stream is zlib's control structure
    memset(&zs, 0, sizeof(zs));

    if (deflateInit(&zs, compressionlevel) != Z_OK)
        throw(std::runtime_error("deflateInit failed while compressing."));

    zs.next_in = (Bytef*)str.data();
    zs.avail_in = str.size();           // set the z_stream's input

    int ret;
    char outbuffer[10240];
    std::string outstring;

    // retrieve the compressed bytes blockwise
    do {
        zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
        zs.avail_out = sizeof(outbuffer);

        ret = deflate(&zs, Z_FINISH);

        if (outstring.size() < zs.total_out) {
            // append the block to the output string
            outstring.append(outbuffer,
                             zs.total_out - outstring.size());
        }
    } while (ret == Z_OK);

    deflateEnd(&zs);

    if (ret != Z_STREAM_END) {          // an error occurred that was not EOF
        std::ostringstream oss;
        oss << "Exception during zlib compression: (" << ret << ") " << zs.msg;
        throw(std::runtime_error(oss.str()));
    }

    return outstring;
}

/** Decompress an STL string using zlib and return the original data. */
std::string myzlib::decompress(const std::string& str) {
    z_stream zs;                        // z_stream is zlib's control structure
    memset(&zs, 0, sizeof(zs));

    if (inflateInit(&zs) != Z_OK)
        throw(std::runtime_error("inflateInit failed while decompressing."));

    zs.next_in = (Bytef*)str.data();
    zs.avail_in = str.size();

    int ret;
    char outbuffer[10240];
    std::string outstring;

    // get the decompressed bytes blockwise using repeated calls to inflate
    do {
        zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
        zs.avail_out = sizeof(outbuffer);

        ret = inflate(&zs, 0);

        if (outstring.size() < zs.total_out) {
            outstring.append(outbuffer,
                             zs.total_out - outstring.size());
        }

    } while (ret == Z_OK);

    inflateEnd(&zs);

    if (ret != Z_STREAM_END) {          // an error occurred that was not EOF
        std::ostringstream oss;
        oss << "Exception during zlib decompression: (" << ret << ") "
            << zs.msg;
        throw(std::runtime_error(oss.str()));
    }

    return outstring;
}

  
myzlib zlib;
  
int main() {
	std::string data = "halloweeks halloweeks halloweeks halloweeks";
	
	std::cout << "Orignal string size: " << data.size() << "\n";
	std::cout << "Original string: " << data << "\n";
	std::cout << "--------------------\n";
	
	// zlib compress data. last parameter is 6 for best compression
	std::string com = zlib.compress(data, 6);
	
	std::cout << "Compressed string size: " << com.size() << "\n";
	std::cout << "Compressed string: " << com << "\n";
	std::cout << "--------------------\n";
	
	// zlib decompress data
	std::string decom = zlib.decompress(com);
	std::cout << "Decompressed string size: " << decom.size() << "\n";
	std::cout << "Decompressed string: " << decom << "\n";
	
	return 0;
}
 ```

# Conan2の使い方
## 全体の流れ
　説明の流れはConan2のチュートリアルに沿います。チュートリアルの流れは以下の通りです。

  - conanfile.txtファイルの作成
    - conanfile.txtは、プロジェクトが依存するライブラリやそのバージョン、ビルドオプションなどの情報が記述される設定ファイルです。
 - Conanプロファイルの作成
    - Conanプロファイルは、コンパイルの設定やビルド設定、アーキテクチャなどの情報が記述される設定ファイルです。
 - ライブラリのインストール
    - conanfile.txtに示したプロジェクトが依存するライブラリをインストールします。

## conanfile.txtファイルの作成
　conanfile.txtファイルをプロジェクトのルートで作成します。このconanfile.txtの書き方は、ほとんどのライブラリは[Conan Center](https://conan.io/center)の最下部辺りに書かれています。

 ```conanfile.txt
[requires]
zlib/1.3.1

[generators]
CMakeDeps
CMakeToolchain
 ```

　conanfile.txtは見ての通り大きく2つのセクションに分けています。それぞれ説明します。

### requires
　requiresセクションは、プロジェクトで使用したいライブラリを宣言する場所です。
　Conan2でインストール可能なライブラリはライブラリ一覧は[Conan Center](https://conan.io/center)で確認できます。また、ライブラリ名を知っているのであれば、コマンドでインストール可能ライブラリを確認できます。以下はzlibを検索しているコマンドの例です。

 ```
conan search zlib
 ```
 
　検索の結果は次のようになります。

 ```
 $ conan search zlib
Found 6 pkg/version recipes matching zlib in conancenter
conancenter
  zlib
    zlib/1.2.8
    zlib/1.2.11
    zlib/1.2.12
    zlib/1.2.13
    zlib/1.3
    zlib/1.3.1
 ```

### generators 
　generatorsセクションは、コンパイラやビルドシステムが依存関係を見つけ、プロジェクトをビルドするために使用するファイルを生成するようにConanに指示します。この場合、私たちのプロジェクトはCMakeをベースにしているので、CMakeDepsを使用してZlibライブラリファイルがインストールされている場所に関する情報を生成し、CMakeToolchainを使用してCMakeツールチェーンファイルを使用してCMakeにビルド情報を渡します。

## Conanプロファイルの作成
　Conanプロファイルは、コンパイラ、ビルド設定、アーキテクチャ、共有ライブラリやスタティックライブラリなどの設定ファイル群をまとめて定義することができます。Conanはデフォルトではプロファイルを自動検出しようとしないので、プロファイルを作成する必要があります。Conanに現在OSのプロファイリングを行わせるために、以下のコマンドを実行する必要があります。

 ```
conan profile detect --force
 ```

## ライブラリのインストール
　conanfile.txtで依存関係を示したライブラリをインストールします。
　conanfile.txtが存在するディレクトリで以下のコマンドを実行してください。

 ```
conan install . --output-folder=build --build=missing
 ```

　もしくは以下のコマンドでも同様の処理が行えます。いずれのパスもconanfile.txtの存在を指し示すことができれば問題ないです。

 ```
conan install conanfile.txt --output-folder=build --build=missing
 ```

　オプションについて解説します。

 ### output-folder
 　output-folderは、インストールされたファイルや生成された設定ファイルを保存するディレクトリを指定します。上記のコマンドではbuildディレクトリを指定しています。

### build
　buildは依存関係のパッケージがビルド済みでない場合に、必要なパッケージをビルドするよう指示します。つまり、上記のコマンドではmissingを指定することで、Conanは依存関係の中でビルド済みのパッケージが見つからない場合に、そのパッケージを自動的にビルドするよう指示しています。
　このオプションはbuildディレクトリを自動で作成してくれます。

 # CMakeの使い方
 ## 全体の流れ
 CMakeを使用してプログラムをコンパイルするため、以下の流れを行う必要がある。
 - CMakeLists.txtファイルの作成
    - CMakeLists.txtはプロジェクトの構造やビルド手順が記述されたファイルである。
 - ビルドディレクトリの作成
    - プロジェクトのビルドに使用するディレクトリを作成します。通常、buildという名前のディレクトリを作成します。
 - CMakeの実行
    - ビルドディレクトリでCMakeを実行します。例えば、cmake /path/to/sourceというコマンドを使用します。これにより、CMakeはCMakeLists.txtファイルを読み込み、ビルドに必要なファイルや設定を生成します。
 - ビルド
    - ビルドディレクトリでcmake --buildというコマンドを使用し、 CMakeが生成したビルドシステム（MakefileやVisual Studioのソリューションファイルなど）を使用して、プロジェクトをビルドします。

## CMakeLists.txtファイルの作成
　CMakeを扱う上で最重要ともいえるCMakeLists.txtファイルの作成方法を説明します。
　プロジェクトのルートディレクトリにCMakeLists.txtを作成します。以下はその例です。

 ```CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(compressor CXX)

find_package(ZLIB REQUIRED)

add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} ZLIB::ZLIB)
```

　各行を説明します。

 ### cmake_minimum_required
 　cmake_minimum_requiredはCMakeの最小バージョンを指定します。このプロジェクトではCMakeバージョン3.16以上が必要です。

### project
　projectはプロジェクト名と使用するプログラム言語を設定します。このプロジェクトではプロジェクト名はcompressorとしています。CXXはC++言語を使用することを指定しています。

### find_package
　find_packageはパッケージを探索します。このプロジェクトで使用するzlibですので、今回はzlibを指定しています。このzlibという語彙は必ずしもプログラムコード中で使用する語彙と同等ではなく、例えばコード中でzlibと書かれていたのが、find_packageでZLIBと大文字で書かれています。この語彙はほとんどのライブラリは[conan center](https://conan.io/center/recipes/zlib?version=1.3.1)の最下部に書いてあるので、好きなライブラリをインストールする際は確認するべきです。

### add_executable
　コンパイルするソースコードのファイル名とコンパイル後の実行可能ファイル名を指定します。

### target_lik_libraries
　ターゲットにConanで解決されたライブラリをリンクします。この場合はZLIB:ZLIBと書いてありますが、この語彙もfind_packageと同じく[conan center](https://conan.io/center/recipes/zlib?version=1.3.1)の最下部に書いてあるので、ライブラリをインストールする際は確認するべきです。

 ## ビルドディレクトリの作成

　Conan2とCMakeを統合する場合、ビルドディレクトリは普通Conan2によって建てられます。それは前述した　Conan2の使い方　にある　ライブラリのインストール　のコマンドを実行するだけで成されます。
　これまでのコマンドが正常に動作しているのなら、「build」ディレクトリが作成されているはずです。

## CMakeの実行
　buildディレクトリをカレントディレクトリとして作業をします。

```
cd build
```

　CMakeを実行しCMakeLists.txtファイルを読み込み、ビルドに必要なファイルや設定を生成します。
　CMakeを実行するため、以下のコマンドを実行します。

 ```
cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
 ```

　DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmakeは、Conanが生成したCMakeツールチェーンファイルを指定しています。DCMAKE_BUILD_TYPE=Releaseは、リリース用のビルドを行うことを指定しています。

 ## ビルド
　カレントディレクトリはbuildのままです。
　プロジェクトをビルドするため以下のコマンドを実行します。

 ```
cmake --build .
 ```

# コマンド
　これまでに出てきたコマンドはそれなりに多いのでいちいちビルドのたびに打っているのでは面倒です。そこでコマンド入力を簡単化するため、Conan2の公式が出しているシェルスクリプトを紹介しましょう。

 ```sh:run.sh
#!/bin/bash

set -e
set -x

BASEDIR=$(dirname "$0")
pushd "$BASEDIR"

rm -rf build

conan install . --output-folder=build --build=missing
cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build .
./compressor
 ```

　プロジェクトディレクトリにmain.cppとCMakeLists.txtとconanfile.txtの隣に並べて配置して、ただ実行するだけでビルドが出来るようになるのですごく便利です。

# 参考

https://docs.conan.io/2/tutorial/consuming_packages/build_simple_cmake_project.html

https://github.com/conan-io/examples2