c/c++を使うならcmake!

を使えるようになろう

前提知識

c/c++をライブラリとして提供するときに必要な奴ら

/include, /lib, だよね。 /includeにはヘッダーが入っていて、/libには、コンパイルによって生成されたライブラリが入ります。hogehoge.soとか、hogehoge.aとか。 で、こいつらは基本的に、/usr/local/いかに格納されます。 ヘッダーについても説明しておく。ヘッダーは簡単にいうと、関数の入出力を規定したファイルです。 プログラムをビルドする流れは、コンパイルー>リンク だけど、コンパイルの時点では、ライブラリとして読み込まれたファイルがどんな

ヘッダーファイルの種類

ヘッダーファイルには2種類ある。

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

みたいな感じで、<>で囲まれているやつと、““で囲まれているやつ。<>は、システムのライブラリを読み込むときに使われます。システムライブラリのヘッダーは、/usr/local/includeとか、 /usr/include/に置かれます。ライブラリ (オブジェクトファイル) 本体は、/usr/libとか、/usr/lib64とかにおかれます。 一方、““はユーザが作ったライブラリを読み込むために使われます。““を使うと、コンパイラはカレントディレクトリの中で該当するヘッダーファイルを探します。 ライブラリとヘッダーは、一対一対応します。つまり、 プログラムからライブラリを読み出すときは必ず、ヘッダーでインクルードして、最後リンクする必要があります。 ユーザが定義したヘッダーでも同じです。まず、hogehoge.hppとhogehoge.cppを作って、コンパイルするときに

g++ main.cpp hogehoge.cpp

みたいな感じで、hogehoge.cppもコンパイルします。オブジェクトができて、最後にリンクしています。 コンパイルー>リンクって流れを覚えておけば問題ないです。 サーチパスを追加したいときは、-Iで、ヘッダーのディレクトリ、-Lでライブラリのディレクトリをつけます。

ld (リンカー) がデフォルトで見に行くディレクトリパス

LD_LIBRARY_PATH

という環境変数で定義されています。これは変えられます。次のようにサーチパスを追加します。

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

ちなみに、ldっていうのは、

Origins of the name "ld" are "LoaDer" and "Link eDitor"

らしいです。知らんけど。

共有ライブラリと静的ライブラリ

共有ライブラリは、実行時に動的に読まれるのに対して、静的ライブラリは、コンパイル時に実行ファイルに組み込まれる。 どちらがいいかはわからないけど、共有ライブラリは、実行ファイル (実行バイナリ) の大きさが小さくなるよね。 ちなみに、共有ライブラリはShared Objectだから、.soっていう拡張子が付きます。一方、静的ライブラリは、 a collection of object files bundled together into a single archive fileなので、arichveの.aが付きます。 つまり、hogehoge.soとhogehoge.aですね。

ここからcmakeの講義です。

まずは、chatGPT先生にcmakeとは?てきいてみた

CMake is an open-source build system and project configuration tool used in software development to simplify the process of building, compiling, and configuring large and complex projects across multiple platforms and compilers. It provides a platform-independent and compiler-independent way to define how a project should be built, making it easier to manage and maintain cross-platform projects.

Here are some key features and aspects of CMake:

Cross-Platform Compatibility: CMake generates platform-specific build files (e.g., Makefiles, Visual Studio project files) from a single, platform-independent configuration. This allows developers to work on different operating systems (such as Windows, Linux, macOS) and build their projects with various compilers (e.g., GCC, Clang, Microsoft Visual C++).

Hierarchical Project Structure: CMake supports a hierarchical project structure, allowing developers to organize large projects into subdirectories with their own CMakeLists.txt files. This makes it easier to manage complex projects by breaking them down into smaller, more manageable parts.

Language-Agnostic: While CMake is often associated with C and C++ projects, it can be used to build projects in other programming languages as well. CMake can generate build files for projects written in various languages, including Python, Java, and more.

Extensibility: CMake supports the creation of custom CMake modules and scripts, enabling developers to extend its functionality to suit their project's specific needs.

Wide Adoption: CMake is widely used in the software development community and is often chosen as the build system for open-source projects. Many popular libraries and applications use CMake for their build process.

Integrated Development Environments (IDE) Integration: CMake can generate project files for various IDEs, including Visual Studio, Xcode, and others, allowing developers to work in their preferred development environment.

CMakeLists.txt Files: CMake projects are configured and managed using CMakeLists.txt files, which specify project details, dependencies, compiler options, and other build-related information. These files are written in a CMake-specific scripting language.

In summary, CMake is a powerful and versatile build system that simplifies the process of building and configuring complex software projects. It provides developers with a unified and consistent way to manage projects across different platforms and compilers, making it a valuable tool for cross-platform development.

ということですね。

じゃあ、まあ、ここからはチートシート的な感じで箇条書きで書いていきます。

まず、cmakeファイルは、CMakeLists.txtに書きます。

参考文献

参考文献は [こちら] (https://usercontent.one/wp/cheatsheet.czutro.ch/wp-content/uploads/2020/09/CMake_Cheatsheet.pdf)です。いい感じにまとめてくれています。 [ライブラリの手動作成方法] (https://qiita.com/shohirose/items/45fb49c6b429e8b204ac) についても、いい感じにまとめてくれています。

minimum_versionの指定

cmake_minimum_required (VERSION 2.9)

プロジェクト名の指定

project (HelloProject)

生成する実行ファイルの指定

# compile and link main.cpp and foo.cpp
add_executable(Hello src/main.cpp src/foo.cpp)

ターゲットは、別に一つである必要はない。 ヘッダーを含める必要はない。というのも、コンパイラーはヘッダーを見つけることができるからね。

cmakeではコマンドが使える。コマンド、というか、関数です。

cmakeではコマンドが使えます。こんな感じです。

COMMAND ( ARGUMENTS GO HERE )

いろいろあるので、あとで紹介したいと思います。

cmakeでは、変数 (variables) も使える。

cmakeでは、変数も定義できます。こんな感じです。

SET( x 3 ) # x = "3"
SET( y 1 ) # y = "1"
MESSAGE( x y ) # displays "xy"
MESSAGE( ${X}${Y} ) # displays "31" 

って感じで、変数を参照するには${} で変数をかこんでください。 また、すべての変数は、text stringです。んで、Text stringsは、booleanとして使うことが可能です。 つまり、IF()や、WHILE()の中で使うことができます。 “FALSE”, “OFF”, “NO”, “*NOTFOUND"はすべて、falseとして評価されます。ほかのtextStringはすべてtrueとして評価されます。

テキストのリストについて

テキストは、SETの中で、スペースを置くことでリストとして分離できます。

cmake_minimum_required (VERSION 2 . 9 )
SET( x 3 2 ) # x = " 3 ; 2 "
SET( y hello world ! ) # y = "hello ; world ; ! " 3つ入っています
SET( z "hello ␣ world ! " ) # y = "hello world !" 1つしか入っていません
MESSAGE( ${x} ) # p r i n t s " xy "
MESSAGE( "y␣=␣${y}␣ z ␣=␣${ z }" )
# prints y = hello ; world ; ! z = hello world !

xには、3と2が入っています。

リストはイテレーションできる

はい、こんな感じでイテレーションできます。

cmake_minimum_required ( VERSION 2.9 )

SET( X 3 2 )
FOREACH (val ${x})
    MESSAGE(${val})
ENDFOREACH(val)

コンパイルオプションの設定

setを使って変数を定義することができるのはわかりましたね。実は、setには、ほかにも使い方があって、 ユーザがコンパイル時にオプションを変更できるようになっています。多分、cmake変数っていうやつです。 これは、文法が決まっています。

set(<variable> <value> CACHE <type> <docstring>)

で、typeに入れることができる変数も以下の5種類となっています。

FILEPATH # File chooser dialog
PATH # Directory chooser dialog
STRING # Arbitary string
BOOL # Boolean ON/OFF
INTERNAL # No GUI entry

実例を見てみましょう。

cmake_minimum_required (VERSION 2.9)

SET(hello true CACHE BOOL "if true write hello")
SET(other_msg "Hi" CACHE STRING "not hellow value")
if (${hello})
    MESSAGE("Hello")
ELSE (${hello})
    MESSAGE(${other_msg})
ENDIF (${hello})

ライブラリの作成

まず、手動で静的ライブラリlibgreetings.aを作成し、main.cppをコンパイルする際にリンクするシェルコマンドを書いてみましょう。

$ g++ -c hello.cpp good_morning.cpp             # オブジェクトファイル(hello.o, good_morning.o)の作成
$ ar rvs libgreetings.a hello.o good_morning.o  # 静的ライブラリ(libgreetings.a)の作成
$ g++ main.cpp libgreetings.a                   # main.cppをコンパイルしてlibgreetings.aとリンクし実行ファイルa.outを作成

続いて、手動で共有ライブラリを作成してみましょう。

$ g++ -fPIC -c hello.cpp good_morning.cpp                 # オブジェクトファイル(hello.o, good_morning.o)の作成
$ g++ -shared hello.o good_morning.o -o libgreetings.so   # 共有ライブラリ(libgreetings.so)の作成
$ g++ main.cpp -L. -lgreetings -Xlinker -rpath -Xlinker . # main.cppをコンパイルしてlibgreetings.soとリンクし実行ファイルa.outを作成

これをcmakeで自動化してみましょう。まずは、静的ライブラリから。

cmake_minimum_required(VERSION 3.13)
project(test_cmake CXX)
# hello.cppとgood_morning.cppをコンパイルして静的ライブラリlibgreetings.aを作成
add_library(greetings STATIC hello.cpp good_morning.cpp)
# a.outという実行ファイルをmain.cppから作成
add_executable(a.out main.cpp)
# a.outを作成する際にlibgreetings.aをリンク
target_link_libraries(a.out greetings)

続いて、共有ライブラリ

cmake_minimum_required(VERSION 3.13)
project(test_cmake CXX)
# hello.cppとgood_morning.cppをコンパイルして共有ライブラリlibgreetings.soを作成
add_library(greetings SHARED hello.cpp good_morning.cpp)
add_executable(a.out main.cpp)
# a.outを作成する際にlibgreetings.soをリンク
target_link_libraries(a.out greetings)

もう分るよね。add_library()でライブラリを作るって話です。共有/静的は、SHARED/STATICで指定することができます。一応、add_library()の文法を書いておくと、こんな感じです。

add_library(<library_name> [SHARED|STATIC] <hogehoge.cpp> <hogehoge2.cpp> ... <hogehogen.cpp>)

って感じですね。ちなみに、このSHARED/STATICは省略することもできます。この時は、グローバルオプションのBUILD_SHARED_LIBSのON/OFFを指定する必要があります。 こんな感じですよね。

cmake .. -DBUILD_SHARED_LIBS=ON

便利ですねー

複雑なプロジェクト (たくさんのサブディレクトリを持つ) をCMAKEで扱う。

コマンド何があるの

add_library()

ライブラリを作るコマンド

target_link_libraries(Hoge Mylibrary) で、HogeにMylibraryをリンクしています。

install()

インストールのルールを生成。使い方は、こんな感じ。

INSTALL(TARGET <ターゲット> DESTINATION <インストールするディレクトリ> EXPORT <エクスポートする場所?>)

みたいな感じで、操作の指定 + 内容 がセットになっている感じ。

include()

Load and run CMake code from a file or module. 3.11移行のcmakeはマジで革新的で、以下のように書くだけで、githubから読み込んできてくれて、ビルドもしてくれる。

include(FetchContent)
FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 9.1.0)
FetchContent_MakeAvailable(fmt)

pymgardでも使っています。

include(FetchContent)
FetchContent_Declare(
    pybind11
    GIT_REPOSITORY https://github.com/pybind/pybind11.git
    GIT_TAG        stable
)
FetchContent_MakeAvailable(pybind11)

グローバルオプション何があるの

まず、オプションの設定方法だけど、こんな感じで、

option(MGARD_ENABLE_CUDA "Enable CUDA support" OFF)

設定可能なオプションとデフォルトの引数が入っています。

グローバルオプション

CMAKE_INSTALL_PREFIX