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

2024-06-17

CMake の "Commands" に関連する "try_compile()" のプログラミング解説

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>]
)

主な引数

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

応用例

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

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() コマンドを連続して実行する場合、同じ出力ディレクトリを使用するため、デバッグには注意が必要です。


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

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: 標準ライブラリの機能を確認する

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()

説明

  • 上記の例では、try_compile() コマンドを使用して、特定の条件が満たされているかどうかを確認しています。
  • 条件が満たされた場合、message() コマンドを使用してメッセージを出力し、その条件に依存する処理を実行しています。
  • 条件が満たされない場合、message() コマンドを使用して警告を出力し、その条件に依存する処理をスキップしています。

補足

  • try_compile() コマンドは、CMake の様々な機能と組み合わせることで、より複雑な条件処理を実現することができます。
  • 詳細については、CMake の公式ドキュメントを参照してください。


CMakeにおける「try_compile()」の代替方法

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

この方法は、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は、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()

最適な代替方法の選択

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

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