【システム固有のコンパイル条件にも対応!】CMake try_compile() でOSやアーキテクチャに応じた処理を行う


try_compile() は、CMake における強力なコマンドであり、特定のソースコードが現在のツールチェーンでコンパイル可能かどうかを確認するために使用されます。主に、以下の2つの用途で活用されます。

  1. コンパイラと標準ライブラリのサポートを確認する
    特定のコンパイラフラグや標準ライブラリ機能がサポートされているかどうかを検証できます。これは、ライブラリやサードパーティ製ソフトウェアの依存関係を処理する際に役立ちます。
  2. システム固有のコンパイル条件を処理する
    オペレーティングシステムやハードウェアアーキテクチャに依存するコンパイルオプションを指定できます。これにより、移植性の高いコードを作成することができます。

基本的な構文

try_compile(
  <result_variable>
  <source_files>
  [COMPILE_DEFINITIONS <compile_definitions>]
  [COMPILE_OPTIONS <compile_options>]
  [COMPILE_FEATURES <compile_features>]
  [LINK_LIBRARIES <link_libraries>]
)

主な引数

  • オプション引数:
    • COMPILE_DEFINITIONS: コンパイル時に定義されるマクロのリスト。
    • COMPILE_OPTIONS: コンパイルオプションのリスト。
    • COMPILE_FEATURES: コンパイラがサポートする機能のリスト。
    • LINK_LIBRARIES: リンク時に使用するライブラリのリスト。
  • <source_files>: コンパイル対象のソースファイルのリスト。
  • <result_variable>: try_compile() の結果を格納する変数。成功 (TRUE) または失敗 (FALSE) が返されます。

応用例

コンパイラフラグのサポートを確認する

try_compile(
  HAS_STD_VECTOR
  main.cpp
  COMPILE_DEFINITIONS STD_VECTOR
)

if(HAS_STD_VECTOR)
  message(STATUS "std::vector is supported")
else()
  message(WARNING "std::vector is not supported")
endif()

標準ライブラリの機能を確認する

try_compile(
  HAS_CXX11
  main.cpp
  COMPILE_FEATURES cxx11
)

if(HAS_CXX11)
  message(STATUS "C++11 is supported")
else()
  message(WARNING "C++11 is not supported")
endif()

システム固有のコンパイル条件を処理する

try_compile(
  IS_WINDOWS
  main.cpp
  COMPILE_OPTIONS /D_WIN32
)

if(IS_WINDOWS)
  message(STATUS "Building for Windows")
else()
  message(STATUS "Building for non-Windows platform")
endif()
  • 複数の try_compile() コマンドを連続して実行する場合、同じ出力ディレクトリを使用するため、デバッグには注意が必要です。
  • try_compile() は、CMake の構成段階で実行されます。
  • try_compile() は、コンパイルのみを行い、実行はしません。


cmake_minimum_required(VERSION 3.10)

project(myproject)

try_compile(
  HAS_STD_VECTOR
  main.cpp
  COMPILE_DEFINITIONS STD_VECTOR
)

if(HAS_STD_VECTOR)
  message(STATUS "std::vector is supported")
  add_executable(myprogram main.cpp)
else()
  message(WARNING "std::vector is not supported")
endif()

例2: 標準ライブラリの機能を確認する

この例では、C++11 がサポートされているかどうかを確認します。

cmake_minimum_required(VERSION 3.10)

project(myproject)

try_compile(
  HAS_CXX11
  main.cpp
  COMPILE_FEATURES cxx11
)

if(HAS_CXX11)
  message(STATUS "C++11 is supported")
  add_executable(myprogram main.cpp)
else()
  message(WARNING "C++11 is not supported")
endif()

例3: システム固有のコンパイル条件を処理する

この例では、オペレーティングシステムに応じて異なるコンパイルオプションを設定します。

cmake_minimum_required(VERSION 3.10)

project(myproject)

try_compile(
  IS_WINDOWS
  main.cpp
  COMPILE_OPTIONS /D_WIN32
)

if(IS_WINDOWS)
  message(STATUS "Building for Windows")
  add_executable(myprogram main.cpp /D_WIN32)
else()
  message(STATUS "Building for non-Windows platform")
  add_executable(myprogram main.cpp)
endif()
  • 条件が満たされない場合、message() コマンドを使用して警告を出力し、その条件に依存する処理をスキップしています。
  • 条件が満たされた場合、message() コマンドを使用してメッセージを出力し、その条件に依存する処理を実行しています。
  • 上記の例では、try_compile() コマンドを使用して、特定の条件が満たされているかどうかを確認しています。
  • 詳細については、CMake の公式ドキュメントを参照してください。
  • try_compile() コマンドは、CMake の様々な機能と組み合わせることで、より複雑な条件処理を実現することができます。


ターゲットを使ったアプローチ

この方法は、CMakeのターゲット機能を利用して、ソースコードのコンパイルを試みます。

利点:

  • キャッシュを活用した効率的な処理
  • 詳細なコンパイルログを出力可能
  • シンプルで分かりやすい構文

欠点:

  • ヘッダーファイルの依存関係を考慮できない場合がある
  • 実際に実行可能なプログラムは生成されない

例:

cmake_minimum_required(VERSION 3.10)

project(myproject)

add_executable(myprogram main.cpp)

target_link_libraries(myprogram some_library)

try_compile(
  RESULT_VAR
  ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)

message(STATUS "Compilation result: ${RESULT_VAR}")

FILE_COMMANDを使ったアプローチ

この方法は、「FILE_COMMAND」コマンドを使って、シェルスクリプトを実行し、コンパイルを試みます。

  • 複雑な条件分岐やエラー処理が可能
  • ヘッダーファイルの依存関係を含む、より詳細なコンパイル環境を構築可能
  • キャッシュを活用できないため、処理速度が遅くなる場合がある
  • 「try_compile()」よりも冗長で複雑な構文
cmake_minimum_required(VERSION 3.10)

project(myproject)

file(COPY main.cpp ${CMAKE_CURRENT_BINARY_DIR}/main.cpp)

file(COMMAND ${CMAKE_CXX_COMPILER} -c main.cpp -o main ${CMAKE_FLAGS} $<$<COMPILE_DEFINITIONS:STD_VECTOR>> -DSTD_VECTOR)

try_compile(
  RESULT_VAR
  ${CMAKE_CURRENT_BINARY_DIR}/main
)

message(STATUS "Compilation result: ${RESULT_VAR}")

CMakeLists.txtの外でテストを行う

この方法は、CMakeLists.txtファイルとは別に、独立したスクリプトでコンパイルテストを行います。

  • テストコードをCMakeLists.txtファイルから分離できる
  • 複雑な条件分岐やループ処理を記述しやすい
  • CMakeプロジェクトとの連携が煩雑になる場合がある
  • CMakeのキャッシュやターゲット機能を活用できない
import subprocess

def try_compile():
  try:
    subprocess.check_call([CMAKE_CXX_COMPILER, "-c", "main.cpp", "-o", "main", CMAKE_FLAGS, "-DSTD_VECTOR"], cwd=CMAKE_CURRENT_BINARY_DIR)
    return True
  except subprocess.CalledProcessError:
    return False

if try_compile():
  print("Compilation successful")
else:
  print("Compilation failed")

Boost.Testを利用する

Boost.Testは、C++向けの単体テストフレームワークであり、「try_compile()」のような機能を提供しています。

  • Boost.Testライブラリを使用する必要がある
  • 詳細なテスト結果を出力可能
  • テストコードを体系的に記述・管理できる
  • 初心者にとって学習コストが高い
  • CMake以外のライブラリを導入する必要がある
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(try_compile_test)

BOOST_AUTO_TEST_CASE(compile_with_std_vector) {
  BOOST_CHECK(BOOST_TEST_COMPILE_FLAGS(CMAKE_FLAGS, "-DSTD_VECTOR", "main.cpp"));
}

BOOST_AUTO_TEST_SUITE_END()

最適な代替方法の選択

上記の代替方法それぞれには、利点と欠点があります。状況に合わせて、最適な方法を選択することが重要です。

  • テストコードを体系的に記述・管理したい場合は、**Boost.Testを利用する
  • 複雑な条件分岐やループ処理が必要な場合は、CMakeLists.txtの外でテストを行う方法が有効です。
  • より詳細なコンパイル環境を構築したい場合は、FILE_COMMANDを使ったアプローチが適しています。
  • シンプルで分かりやすい方法を求める場合は、ターゲットを使ったアプローチがおすすめです。