CMakeのtarget_compile_options()徹底解説:基本から応用まで

2025-05-27

目的

このコマンドの主な目的は、プロジェクト内の特定のターゲットに対して、コンパイラに渡すオプションを細かく制御することです。例えば、特定の警告フラグ(-Wall)、最適化レベル(-O3)、特定のC++標準(-std=c++17)などを指定できます。

構文

基本的な構文は以下のようになります。

target_compile_options(<target> [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
  • [items...]: 実際にコンパイラに渡すオプションを指定します。複数のオプションをスペース区切りで指定できます。

  • <INTERFACE|PUBLIC|PRIVATE>: これは、追加するコンパイラオプションの「スコープ」または「可視性」を定義する重要なキーワードです。

    • PRIVATE:

      • オプションは、そのターゲット自身がコンパイルされるときにのみ適用されます。
      • そのターゲットに依存する他のターゲットには、このオプションは伝播されません。
      • 例: target_compile_options(MyLibrary PRIVATE -Wno-unused-variable) (MyLibrary自身のコンパイル時のみ、未使用変数に関する警告を抑制する)
    • PUBLIC:

      • オプションは、そのターゲット自身がコンパイルされるときに適用されます。
      • さらに、そのターゲットに依存する他のターゲットにも、このオプションが伝播されます。つまり、MyLibraryPUBLICオプションを追加し、別のターゲットMyExecutableMyLibraryにリンクする場合、MyExecutableもそのオプションを受け取ります。
      • 例: target_compile_options(MyLibrary PUBLIC -DENABLE_FEATURE) (MyLibrary自身と、MyLibraryにリンクするすべてのターゲットでENABLE_FEATUREマクロを定義する)
    • INTERFACE:

      • オプションは、そのターゲット自身がコンパイルされるときには適用されません
      • そのターゲットに依存する他のターゲットにのみ、このオプションが伝播されます。
      • 主にヘッダーオンリーライブラリなど、コンパイル済みのコードを持たないが、利用する側に特定のコンパイラオプションを要求する場合に使用されます。
      • 例: target_compile_options(MyHeaderOnlyLib INTERFACE -I${Boost_INCLUDE_DIRS}) (MyHeaderOnlyLib自体にはコンパイルするものがないが、これに依存するターゲットにBoostのインクルードディレクトリを伝える)
  • [BEFORE]: このキーワードを指定すると、指定されたオプションが既存のオプションの前に挿入されます。

  • <target>: オプションを追加するターゲットの名前を指定します。このターゲットは、add_executable()add_library()などで事前に作成されている必要があります。

# 実行ファイルを作成
add_executable(my_app main.cpp)

# my_appに対してPRIVATEなコンパイラオプションを追加
# -Wall: すべての警告を有効にする
# -Wextra: 追加の警告を有効にする
target_compile_options(my_app PRIVATE -Wall -Wextra)

# ライブラリを作成
add_library(my_lib STATIC my_lib.cpp my_lib.h)

# my_libに対してPUBLICなコンパイラオプションを追加
# -fPIC: 位置独立コードを生成(共有ライブラリでよく使われる)
target_compile_options(my_lib PUBLIC -fPIC)

# my_libに依存するmy_app
target_link_libraries(my_app my_lib)

# 別のライブラリを作成(ヘッダーオンリーと仮定)
add_library(my_interface_lib INTERFACE)
# my_interface_libに依存するターゲットがC++17標準を要求するように設定
target_compile_options(my_interface_lib INTERFACE -std=c++17)

# my_appがmy_interface_libに依存
target_link_libraries(my_app my_interface_lib)

この例では、my_app-Wall-Wextraでコンパイルされ、my_lib-fPICでコンパイルされます。また、my_appmy_libにリンクしているため、my_appのコンパイル時にも-fPICが適用されます。さらに、my_appmy_interface_libにリンクしているため、my_appのコンパイル時にも-std=c++17が適用されます。

  • より具体的なコマンド: プリプロセッサ定義(-D)を追加する場合はtarget_compile_definitions()、インクルードディレクトリ(-I)を追加する場合はtarget_include_directories()を使用することが推奨されます。これらのコマンドは、target_compile_options()よりも意図が明確になります。
  • add_compile_options()との違い: add_compile_options()はカレントディレクトリとそのサブディレクトリ内のすべてのターゲットにコンパイラオプションを適用します。一方、target_compile_options()は特定のターゲットにのみ適用されるため、より細かく制御できます。
  • ジェネレータ式 (Generator Expressions): target_compile_options()では、ビルド設定(Debug/Releaseなど)やコンパイラの種類に基づいて異なるオプションを適用するために、ジェネレータ式を使用できます。 例: target_compile_options(MyTarget PRIVATE "$<$<CONFIG:Debug>:-g> $<$<CONFIG:Release>:-O3>")


ターゲットが存在しない (Target does not exist)

エラーメッセージ例

CMake Error at CMakeLists.txt:10 (target_compile_options):
  Cannot specify compile options for target "MyNonExistentTarget" which is not
  built by this project.

原因
target_compile_options()を呼び出す前に、指定したターゲット(例: MyNonExistentTarget)がadd_executable()add_library()で定義されていない場合に発生します。

トラブルシューティング

  • ターゲットが別のCMakeLists.txtファイルで定義されている場合、そのファイルがadd_subdirectory()などで正しく含まれているか確認してください。
  • ターゲットの名前のスペルミスがないか確認してください。
  • target_compile_options()を呼び出す前に、対応するadd_executable()またはadd_library()が実行されていることを確認してください。

オプションが適用されない、または期待通りに機能しない

原因
これは最も一般的な問題の一つで、いくつかの原因が考えられます。

a. スコープ(PRIVATE/PUBLIC/INTERFACE)の誤解

  • INTERFACE: オプションはターゲット自身には適用されず、依存するターゲットにのみ伝播します。 このスコープの理解が間違っていると、期待するターゲットにオプションが適用されないことがあります。
  • PUBLIC: オプションはターゲット自身と、それに依存するターゲットの両方に適用されます。
  • PRIVATE: オプションはターゲット自身にのみ適用され、依存するターゲットには伝播しません。

b. オプションの順序の問題
コンパイラオプションの中には、順序が重要なものがあります。例えば、-D(定義)と-U(未定義)が競合する場合などです。CMakeはオプションを結合しますが、その順序が意図しない結果を生むことがあります。

c. コンパイラ固有のオプション
指定したオプションが現在のコンパイラ(GCC, Clang, MSVCなど)でサポートされていない場合、無視されたり、警告やエラーが出たりすることがあります。

d. キャッシュの問題
CMakeはビルド設定をキャッシュするため、CMakeCache.txtが古い情報を持っている場合があります。

トラブルシューティング

  • キャッシュのクリア
    ビルドディレクトリを削除し、CMakeを最初から再実行(cmake ..など)することで、キャッシュの問題を解決できる場合があります。
  • コンパイラ固有のオプションの調整
    CMAKE_CXX_COMPILER_IDなどの変数を使って、コンパイラによってオプションを条件分岐させることができます。
    if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
        target_compile_options(my_app PRIVATE -Wno-unused-variable)
    elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
        target_compile_options(my_app PRIVATE /W0)
    endif()
    
  • get_target_property()での確認
    CMakeLists.txt内で、get_target_property(OPTIONS_VAR <target_name> COMPILE_OPTIONS) を使用して、実際にターゲットに設定されているコンパイラオプションを取得し、message()で出力して確認できます。
    get_target_property(MY_APP_OPTIONS my_app COMPILE_OPTIONS)
    message(STATUS "Options for my_app: ${MY_APP_OPTIONS}")
    
  • 詳細なビルド出力の確認
    • Makefilesの場合: make VERBOSE=1 または make VERBOSE=ON
    • Ninjaの場合: ninja -v これらのコマンドを実行すると、実際のコンパイルコマンドが表示され、そこに指定したオプションが含まれているかを確認できます。
  • スコープの確認
    PRIVATE, PUBLIC, INTERFACEキーワードのどれが適切か、もう一度確認してください。特に、別のターゲットがそのオプションを必要とする場合はPUBLICまたはINTERFACEを使用し、自身のターゲットのみであればPRIVATEを使用します。

リンカオプションをコンパイラオプションとして指定してしまう

エラーメッセージ例
(直接的なCMakeエラーではなく、コンパイル/リンク時のエラーとして現れることが多い)

/usr/bin/ld: cannot find -lSomeLibrary
collect2: error: ld returned 1 exit status

または、コンパイラが認識しないフラグに関する警告/エラー。

原因
target_compile_options()はコンパイラに渡すオプションを設定するものであり、リンカに渡すオプションを設定するものではありません。例えば、-l<library>-L<path>のようなリンカオプションをtarget_compile_options()で指定すると、コンパイラがそれらを理解できずにエラーになったり、無視されたりします。

トラブルシューティング

  • どのオプションがコンパイラ用で、どのオプションがリンカ用かを明確に理解することが重要です。一般的に、-D-I-W-Oなどはコンパイラオプションであり、-l-L-Wl,で始まるものはリンカオプションであることが多いです。
  • リンカオプションはtarget_link_options()target_link_libraries()target_link_directories()などの専用のコマンドを使用してください。

add_compile_options()とtarget_compile_options()の混同

原因

  • target_compile_options()は、特定のターゲットのみに適用されます。
  • add_compile_options()は、それを呼び出したCMakeLists.txtとそのサブディレクトリにあるすべてのターゲットに適用されます。ただし、そのコマンドが呼び出された後に定義されたターゲットにのみ影響します。

この違いを理解していないと、意図しないターゲットにオプションが適用されたり、逆に適用されなかったりします。

トラブルシューティング

  • add_compile_options()を呼び出すタイミングに注意してください。通常は、project()コマンドの直後や、ターゲット定義の前に配置します。
  • 特定のターゲットにのみオプションを適用したい場合は、必ずtarget_compile_options()を使用します。
  • 全てのターゲットに共通のオプションを適用したい場合は、トップレベルのCMakeLists.txtでadd_compile_options()を使用します。

ジェネレータ式 (Generator Expressions) の構文エラー

エラーメッセージ例

CMake Error in CMakeLists.txt:
  Error in cmake code:
    $<CONFIG:Debug:-g>
  Expected expression.

原因
ジェネレータ式は強力ですが、構文が厳密です。閉じ括弧の不足、コロンの誤用、不適切なコンテキストでの使用などがエラーの原因となります。

トラブルシューティング

  • 小さなテストケースでジェネレータ式が正しく機能するか確認してみるのも良い方法です。
  • 特に、ネストされたジェネレータ式(例: "$<$<CONFIG:Debug>:-g>")の場合、引用符の配置に注意が必要です。
  • ジェネレータ式の構文($<condition:value>$<target_property:target,property>など)を再確認してください。

変数の展開の問題

原因
オプションに変数を指定する際に、変数が期待通りに展開されていない、または空になっている場合があります。

  • if()文などで変数の存在を確認することも有効です。
  • 変数が定義される前にtarget_compile_options()が呼び出されていないか確認します。
  • message(STATUS "My variable value: ${MY_VAR}") を使用して、target_compile_options()に渡す変数の値が正しいことを確認します。
  • コミュニティでの検索
    Stack OverflowやCMakeのフォーラムなど、他の開発者が同じ問題に遭遇し、解決策を見つけている場合があります。
  • 公式ドキュメントの参照
    target_compile_optionsの公式ドキュメントは、最も正確で最新の情報源です。疑問が生じた場合はまず参照してください。
  • CMakeのバージョン確認
    使用しているCMakeのバージョンによっては、特定の機能や構文がサポートされていない場合があります。公式ドキュメントでバージョンごとの違いを確認してください。
  • 最小再現ケースの作成
    問題が発生した場合、その原因を特定するために、問題の現象を再現できる最小限のCMakeLists.txtファイルとソースコードを作成してみてください。これにより、無関係な設定が問題に影響するのを排除できます。


例1: 基本的な使用法とスコープ (PRIVATE, PUBLIC, INTERFACE)

この例では、my_executableという実行ファイルとmy_libraryというスタティックライブラリを作成し、それぞれに異なるスコープでコンパイラオプションを適用します。

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(CompileOptionsExample CXX)

# --- ターゲットの定義 ---
add_executable(my_executable main.cpp)
add_library(my_library STATIC my_library.cpp my_library.h)
add_library(my_header_only_lib INTERFACE) # ヘッダーオンリーライブラリの例

# --- コンパイラオプションの設定 ---

# 1. PRIVATE スコープの例: my_executable 自身にのみ適用
#    -Wall: すべての警告を有効にする
#    -Wextra: 追加の警告を有効にする
target_compile_options(my_executable PRIVATE -Wall -Wextra)
message(STATUS "my_executable: PRIVATE options set (-Wall -Wextra)")

# 2. PUBLIC スコープの例: my_library 自身と、my_library に依存するターゲットに適用
#    -DENABLE_FEATURE: ENABLE_FEATURE マクロを定義
#    -fPIC: 位置独立コードを生成 (共有ライブラリでよく使われるが、スタティックライブラリでも設定可能)
target_compile_options(my_library PUBLIC -DENABLE_FEATURE -fPIC)
message(STATUS "my_library: PUBLIC options set (-DENABLE_FEATURE -fPIC)")

# 3. INTERFACE スコープの例: my_header_only_lib に依存するターゲットにのみ適用
#    -std=c++17: C++17 標準を使用
target_compile_options(my_header_only_lib INTERFACE -std=c++17)
message(STATUS "my_header_only_lib: INTERFACE options set (-std=c++17)")

# --- ターゲット間の依存関係の設定 ---

# my_executable が my_library にリンク
# my_library の PUBLIC オプションが my_executable に伝播する
target_link_libraries(my_executable my_library)
message(STATUS "my_executable links my_library.")

# my_executable が my_header_only_lib にリンク (または依存)
# my_header_only_lib の INTERFACE オプションが my_executable に伝播する
target_link_libraries(my_executable my_header_only_lib)
message(STATUS "my_executable links my_header_only_lib.")

# --- 設定されているオプションの確認 (デバッグ用) ---
# CMake 3.13 以降で利用可能な COMPILE_OPTIONS プロパティ
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
    get_target_property(EXEC_COMPILE_OPTIONS my_executable COMPILE_OPTIONS)
    message(STATUS "Effective compile options for my_executable: ${EXEC_COMPILE_OPTIONS}")

    get_target_property(LIB_COMPILE_OPTIONS my_library COMPILE_OPTIONS)
    message(STATUS "Effective compile options for my_library: ${LIB_COMPILE_OPTIONS}")
else()
    message(WARNING "CMAKE_VERSION is less than 3.13. Cannot get COMPILE_OPTIONS directly.")
endif()

main.cpp

#include <iostream>
#include "my_library.h" // my_library に依存

int main() {
    std::cout << "Hello from main.cpp" << std::endl;

#ifdef ENABLE_FEATURE
    std::cout << "ENABLE_FEATURE is defined!" << std::endl;
#else
    std::cout << "ENABLE_FEATURE is NOT defined." << std::endl;
#endif

    MyLibraryFunction(); // my_library の関数を呼び出し

    // C++17 固有のコード (my_header_only_lib の INTERFACE オプションが効いているか確認)
    if constexpr (true) { // C++17 の if constexpr
        std::cout << "C++17 is enabled (if constexpr)." << std::endl;
    }

    return 0;
}

my_library.h

#pragma once

void MyLibraryFunction();

my_library.cpp

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

void MyLibraryFunction() {
    std::cout << "Hello from MyLibraryFunction!" << std::endl;
}

実行結果の予想

  • 最終的に実行されるmy_executableは、"ENABLE_FEATURE is defined!""C++17 is enabled (if constexpr)."を出力します。
  • main.cppは以下のオプションでコンパイルされます。
    • my_executablePRIVATEオプション: -Wall -Wextra
    • my_libraryPUBLICオプションが伝播したもの: -DENABLE_FEATURE-fPIC
    • my_header_only_libINTERFACEオプションが伝播したもの: -std=c++17
  • my_library.cpp-DENABLE_FEATURE-fPICでコンパイルされます。

例2: デバッグ/リリースビルドでのオプションの切り替え (ジェネレータ式)

ビルドタイプ(Debug/Releaseなど)に基づいて異なるコンパイラオプションを適用する一般的なパターンです。ジェネレータ式を使用します。

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(BuildTypeOptionsExample CXX)

add_executable(my_app_with_config main.cpp)

# デバッグビルドではデバッグ情報 (-g) を追加
# リリースビルドでは最適化 (-O3) とデバッグ情報の削除 (-s) を追加
target_compile_options(my_app_with_config PRIVATE
    "$<$<CONFIG:Debug>:-g>"
    "$<$<CONFIG:Release>:-O3;-s>"
)

# 別の方法として、環境変数 CMAKE_BUILD_TYPE を使って設定することも可能
# set(CMAKE_CXX_FLAGS_DEBUG "-g")
# set(CMAKE_CXX_FLAGS_RELEASE "-O3 -s")
# ただし、target_compile_options を使う方が、よりターゲットごとに細かく制御できる

message(STATUS "Config-specific options set for my_app_with_config.")

# 確認 (ビルド時に実際に適用されるオプションは、ビルドディレクトリで make VERBOSE=1 などで確認)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
    get_target_property(APP_CONFIG_OPTIONS my_app_with_config COMPILE_OPTIONS)
    message(STATUS "Effective compile options for my_app_with_config (raw): ${APP_CONFIG_OPTIONS}")
endif()

main.cpp

#include <iostream>

int main() {
    std::cout << "Hello from config-aware app!" << std::endl;
    // デバッグビルドでコンパイルされた場合、デバッガでステップ実行可能
    // リリースビルドでコンパイルされた場合、最適化が適用される
    return 0;
}

ビルドと確認

  1. mkdir build_debug
    cd build_debug
    cmake .. -DCMAKE_BUILD_TYPE=Debug
    cmake --build . --verbose # コンパイルコマンドに -g が含まれることを確認
    
  2. Release ビルド

    mkdir build_release
    cd build_release
    cmake .. -DCMAKE_BUILD_TYPE=Release
    cmake --build . --verbose # コンパイルコマンドに -O3 と -s が含まれることを確認
    

例3: 特定のコンパイラに対するオプション

コンパイラ(GCC/Clang, MSVCなど)によって異なるオプションを適用したい場合に便利です。

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(CompilerSpecificOptions CXX)

add_executable(my_compiler_specific_app main.cpp)

# GCC/Clang (GNU-like compilers)
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(my_compiler_specific_app PRIVATE
        -Werror # 警告をエラーとして扱う
        -fdiagnostics-color=always # 診断メッセージに色を付ける
    )
    message(STATUS "GNU/Clang specific options applied.")
endif()

# MSVC (Microsoft Visual C++)
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
    target_compile_options(my_compiler_specific_app PRIVATE
        /WX # 警告をエラーとして扱う
        /Zi # プログラムデータベース情報を生成 (デバッグ用)
        /external:W0 # 外部ヘッダーの警告を抑制 (Visual Studio 2017 15.6以降)
    )
    message(STATUS "MSVC specific options applied.")
endif()

# どちらのコンパイラでも共通のオプション
target_compile_options(my_compiler_specific_app PRIVATE -DPREFIX="Hello")
message(STATUS "Common options applied (-DPREFIX).")

main.cpp

#include <iostream>

int main() {
    std::cout << PREFIX << std::endl; // -DPREFIX="Hello" で定義される
    return 0;
}

実行結果の予想

  • どちらのコンパイラでも、"Hello"が出力されます。
  • MSVCでビルドすると、/WXなどが適用されます。
  • GCC/Clangでビルドすると、-Werrorなどが適用されます。

target_compile_options()は汎用的なオプションを設定するために使われますが、target_compile_definitions()target_include_directories()のように、より具体的な目的のための専用コマンドが存在します。これらのコマンドは、target_compile_options()を使って-D-Iフラグを直接指定するよりも推奨されます。

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(SpecificCommandsExample CXX)

add_executable(my_app_with_specifics main.cpp)
add_library(my_lib_specific STATIC my_lib_specific.cpp my_lib_specific.h)

# --- compile_options() を使用する場面 ---
# 汎用的なフラグ、警告抑制など
target_compile_options(my_app_with_specifics PRIVATE
    -Wshadow # 変数のシャドウイングに関する警告
)

# --- compile_definitions() を使用する場面 ---
# プリプロセッサ定義を追加
target_compile_definitions(my_lib_specific PUBLIC
    MY_LIB_VERSION="1.0.0" # 文字列の定義
    MY_DEBUG_MODE # フラグの定義
)
message(STATUS "my_lib_specific: definitions set (MY_LIB_VERSION, MY_DEBUG_MODE).")

# --- include_directories() を使用する場面 ---
# インクルードパスを追加
# PUBLICスコープなので、my_lib_specific に依存する my_app_with_specifics もこのパスを使う
target_include_directories(my_lib_specific PUBLIC
    "${CMAKE_CURRENT_SOURCE_DIR}/includes" # 別のインクルードディレクトリを追加
)
message(STATUS "my_lib_specific: include directory set.")

# my_app_with_specifics が my_lib_specific に依存
target_link_libraries(my_app_with_specifics my_lib_specific)

main.cpp

#include <iostream>
#include "my_lib_specific.h" // my_lib_specific のヘッダー

#include "extra_header.h" // includes/extra_header.h にあると仮定

int main() {
    std::cout << "Hello from app with specifics!" << std::endl;

#ifdef MY_DEBUG_MODE
    std::cout << "MY_DEBUG_MODE is enabled." << std::endl;
#endif

    std::cout << "My library version: " << MY_LIB_VERSION << std::endl;

    print_extra_message(); // extra_header.h の関数

    MySpecificLibFunction();
    return 0;
}

my_lib_specific.h

#pragma once
void MySpecificLibFunction();

my_lib_specific.cpp

#include "my_lib_specific.h"
#include <iostream>

void MySpecificLibFunction() {
    std::cout << "Hello from MySpecificLibFunction!" << std::endl;
}

includes/extra_header.h (新しく作成)

#pragma once
#include <iostream>

inline void print_extra_message() {
    std::cout << "This is an extra message from an included directory." << std::endl;
}

実行結果の予想

  • プログラムを実行すると、"MY_DEBUG_MODE is enabled.""My library version: 1.0.0""This is an extra message..."が出力されます。
  • main.cppのコンパイル時には、my_lib_specificから伝播したMY_DEBUG_MODEMY_LIB_VERSIONが定義され、includesディレクトリがインクルードパスに含まれます。


add_compile_options()

説明
add_compile_options()は、それを呼び出したディレクトリとそのサブディレクトリにある、そのコマンドが呼び出された後に定義されたすべてのターゲットにコンパイラオプションを追加します。

使用例

cmake_minimum_required(VERSION 3.10)
project(AddCompileOptionsExample CXX)

# このコマンドより後に定義される全てのターゲットに適用
add_compile_options(-Werror -Wextra)

add_executable(my_app1 main1.cpp)
add_executable(my_app2 main2.cpp) # my_app1 と my_app2 の両方に -Werror -Wextra が適用される

# このコマンドより前に定義されたターゲットには影響しない
# add_executable(my_app_before_add_options pre_main.cpp) # これは -Werror -Wextra が適用されない

add_subdirectory(subdir) # subdir/CMakeLists.txt 内のターゲットにも適用される

メリット

  • コードが簡潔になる。
  • プロジェクト全体、または特定のサブツリー内の多数のターゲットに共通のオプションを簡単に適用できる。

デメリット

  • 特定のターゲットにだけ適用したい場合に不向き。
  • オプションの順序が重要になる場合、target_compile_options()のような細かい制御が難しい。
  • 適用範囲が広いため、意図しないターゲットにオプションが適用されてしまう可能性がある。

使い分け

  • 通常はトップレベルのCMakeLists.txtで呼び出されます。
  • プロジェクト全体で一貫した警告設定(例: -Wall -Wextra)やデバッグフラグ(例: -g)を適用したい場合に適しています。

set(CMAKE_CXX_FLAGS ...) や set(CMAKE_C_FLAGS ...) (非推奨だが存在)

説明
CMAKE_CXX_FLAGSCMAKE_C_FLAGSといった変数を直接設定することで、グローバルなコンパイラオプションを設定できます。これはCMakeの初期のバージョンでよく使われていた方法ですが、現在はより粒度の高いコマンドが推奨されています。

使用例

cmake_minimum_required(VERSION 3.10)
project(GlobalFlagsExample CXX)

# グローバルなC++コンパイラフラグを設定
# 通常はプロジェクトのトップレベルで設定される
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG")

# デバッグビルドとリリースビルドで異なるフラグを設定する場合
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG")

add_executable(my_app main.cpp) # これに上記のフラグが適用される

メリット

  • 古いCMakeプロジェクトとの互換性。
  • 非常に広範な影響範囲を持つ。

デメリット

  • カスケード的にオプションが上書きされたり、予期せぬ結合が起きたりする可能性がある。
  • 他のCMakeコマンド(add_compile_options()target_compile_options())と組み合わせると、オプションの順序や重複が複雑になる可能性がある。
  • ターゲットごとの制御が不可能。
  • 非推奨
    CMakeの最新のプラクティスでは推奨されません。

使い分け

  • 既存の非常に古いプロジェクトで、変更が難しい場合にのみ検討されることがあります。
  • 基本的には使用を避けるべきです。代わりにadd_compile_options()target_compile_options()を使用してください。

target_compile_definitions()

説明
これはtarget_compile_options()の特殊なケースであり、プリプロセッサ定義(-Dオプション)を追加するためだけに設計されています。target_compile_options(MyTarget PRIVATE -DMACRO=value) と同じ効果を持ちますが、より意図が明確になります。スコープ(PRIVATE/PUBLIC/INTERFACE)も同様に機能します。

使用例

add_library(my_lib STATIC my_lib.cpp)

# my_lib 自身と依存するターゲットで DEBUG_BUILD を定義
target_compile_definitions(my_lib PUBLIC DEBUG_BUILD)

# my_lib 自身でのみ VERSION を定義
target_compile_definitions(my_lib PRIVATE VERSION="1.0.0")

メリット

  • プリプロセッサ定義に特化しているため、間違いが少ない。
  • target_compile_options()を使うよりもコードの意図が明確になる。

デメリット

  • プリプロセッサ定義以外のコンパイラオプション(警告フラグ、最適化レベルなど)は設定できない。

使い分け

  • プリプロセッサマクロ(-Dフラグ)を設定する際には、常にこのコマンドを優先的に使用するべきです。

target_include_directories()

説明
これもtarget_compile_options()の特殊なケースで、インクルードパス(-Iオプション)を追加するためだけに設計されています。スコープ(PRIVATE/PUBLIC/INTERFACE)も同様に機能します。

使用例

add_library(my_lib STATIC my_lib.cpp)

# my_lib 自身と依存するターゲットに "path/to/headers" をインクルードパスとして追加
target_include_directories(my_lib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # ビルド時
    $<INSTALL_INTERFACE:include> # インストール時
)

# my_lib 自身でのみ "another/local/path" をインクルードパスとして追加
target_include_directories(my_lib PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}/generated_headers
)

メリット

  • ビルド時とインストール時のパスを区別するBUILD_INTERFACEINSTALL_INTERFACEといった便利なジェネレータ式が利用しやすい。
  • インクルードパスに特化しているため、間違いが少ない。
  • target_compile_options()を使うよりもコードの意図が明確になる。

デメリット

  • インクルードパス以外のコンパイラオプションは設定できない。

使い分け

  • インクルードディレクトリ(-Iフラグ)を設定する際には、常にこのコマンドを優先的に使用するべきです。

説明
CMakeを実行する前に、シェル環境でCXXFLAGSCFLAGSといった環境変数を設定することも可能です。CMakeはこれらの変数の内容を、プロジェクトのコンパイラオプションに自動的に追加しようとします。

使用例 (シェル)

export CXXFLAGS="-Wall -Wextra -O2"
cmake .
make

メリット

  • CI/CD環境や、一時的なデバッグ目的で特定のフラグを追加したい場合に便利。
  • CMakeLists.txtを変更せずに、外部からビルドオプションを注入できる。

デメリット

  • CMakeのビルドシステムに統合されていないため、IDEのプロジェクト設定などでは反映されないことがある。
  • CMakeLists.txt内で明示的に定義されたオプションと競合したり、予期せぬ結合が起きたりする可能性がある。
  • ターゲットごとの細かな制御が不可能。
  • ビルドの再現性が損なわれる可能性がある(環境に依存するため)。
  • プロジェクトの永続的な設定としては推奨されません。
  • 基本的には開発者が一時的に特定のフラグを試したい場合や、CI/CDパイプラインで標準的なオプションを適用したい場合に限定して使用するべきです。
方法適用範囲粒度推奨度メリットデメリット
target_compile_options()特定のターゲットターゲットごとの精密な制御各ターゲットで設定が必要
add_compile_options()カレントディレクトリ以下(後続ターゲット)多数のターゲットに共通設定を適用しやすい適用範囲が広すぎる可能性
set(CMAKE_CXX_FLAGS)グローバル(全ターゲット)古いプロジェクトとの互換性非推奨、制御が粗い、予期せぬ動作の可能性
target_compile_definitions()特定のターゲットプリプロセッサ定義に特化し、意図が明確他のオプションは設定不可
target_include_directories()特定のターゲットインクルードパスに特化し、意図が明確他のオプションは設定不可
環境変数グローバルCMakeLists.txt変更不要再現性低下、制御が粗い、IDE統合の問題など