【CMake】PROJECT_IS_TOP_LEVELを使いこなす!トップレベルかどうかを判定して処理を制御


PROJECT_IS_TOP_LEVEL は CMake における組み込み変数であり、現在処理されている CMakeLists.txt ファイルがプロジェクトのトップレベルのものかどうかを真偽値で示します。これは、CMake 3.21 以降で導入された比較的新しい変数です。

用途

この変数は、主に以下の用途に役立ちます。

  • サブディレクトリの処理を制御する: add_subdirectory() コマンドでサブディレクトリを追加する場合、そのサブディレクトリ内に project() コマンドが存在するかどうかを判定するのに役立ちます。
  • 外部プロジェクトの依存関係を管理する: FetchContent モジュールを使用して外部プロジェクトを追加する場合、PROJECT_IS_TOP_LEVEL を用いてトップレベルプロジェクトのみで依存関係を処理することができます。
  • プロジェクト固有のビルド設定を適用する: トップレベルの CMakeLists.txt ファイルでのみ実行される処理を定義したい場合に便利です。

動作

PROJECT_IS_TOP_LEVEL の値は、以下のいずれかの場合に TRUE になります。

  • add_subdirectory() で追加されたサブディレクトリ (ただし、そのサブディレクトリ内に project() コマンドが存在しない場合): add_subdirectory() コマンドでサブディレクトリを追加した場合、そのサブディレクトリ内の CMakeLists.txt ファイルが処理されている場合。ただし、そのサブディレクトリ内に project() コマンドが存在する場合は FALSE になります。
  • ExternalProject で追加された外部プロジェクトのトップレベル: FetchContent モジュールを使用して追加された外部プロジェクトのルートディレクトリにある CMakeLists.txt ファイルが処理されている場合。
  • トップレベルの CMakeLists.txt ファイル: プロジェクトのルートディレクトリにある CMakeLists.txt ファイルが処理されている場合。
  • サブディレクトリ内に複数の CMakeLists.txt ファイルが存在する場合、PROJECT_IS_TOP_LEVELそのサブディレクトリ内で最初に処理された CMakeLists.txt ファイルに基づいて値が決定されます。
  • PROJECT_IS_TOP_LEVEL は、現在のディレクトリスコープまたはそれより上位における 最も最近呼び出された project() コマンド に基づいて値が決定されます。

以下の例は、PROJECT_IS_TOP_LEVEL を使用してプロジェクト固有のビルド設定を適用する方法を示しています。

cmake_minimum_required(VERSION 3.21)

project(MyProject)

if(PROJECT_IS_TOP_LEVEL)
  # トップレベルプロジェクトの場合のみ実行される処理
  set(CMAKE_BUILD_TYPE Debug)
  set(CMAKE_CXX_FLAGS "-Wall -O0")
endif()

add_executable(my_app main.cpp)

この例では、CMAKE_BUILD_TYPECMAKE_CXX_FLAGS はトップレベルプロジェクトの場合のみ設定されます。



cmake_minimum_required(VERSION 3.21)

project(MyProject)

if(PROJECT_IS_TOP_LEVEL)
  # トップレベルプロジェクトの場合のみ実行される処理
  set(CMAKE_BUILD_TYPE Debug)
  set(CMAKE_CXX_FLAGS "-Wall -O0")
endif()

add_executable(my_app main.cpp)

説明

この例では、PROJECT_IS_TOP_LEVEL を使用して、トップレベルプロジェクトの場合のみ CMAKE_BUILD_TYPECMAKE_CXX_FLAGS を設定しています。

例 2: 外部プロジェクトの依存関係を管理する

cmake_minimum_required(VERSION 3.21)

project(MyProject)

if(PROJECT_IS_TOP_LEVEL)
  # トップレベルプロジェクトの場合のみ実行される処理
  fetch_content_make_available(MyExternalProject)
endif()

add_executable(my_app main.cpp)
target_link_libraries(my_app MyExternalProject)

説明

この例では、PROJECT_IS_TOP_LEVEL を使用して、FetchContent モジュールで追加された外部プロジェクトの依存関係をトップレベルプロジェクトのみで処理しています。

例 3: サブディレクトリの処理を制御する

cmake_minimum_required(VERSION 3.21)

project(MyProject)

add_subdirectory(lib1)
add_subdirectory(lib2)

if(PROJECT_IS_TOP_LEVEL)
  # トップレベルプロジェクトの場合のみ実行される処理
  add_executable(my_app main.cpp lib1/lib1.a lib2/lib2.a)
endif()

説明

この例では、PROJECT_IS_TOP_LEVEL を使用して、add_subdirectory() で追加されたサブディレクトリを処理するかどうかを制御しています。lib1 サブディレクトリ内に project() コマンドが存在するため、lib1 はサブディレクトリとして処理され、トップレベルプロジェクトにはなりません。一方、lib2 サブディレクトリ内に project() コマンドが存在しないため、lib2 はトップレベルプロジェクトとして処理されます。



しかし、状況によっては PROJECT_IS_TOP_LEVEL の代替方法が必要となる場合があります。以下に、いくつかの代替方法をご紹介します。

CMAKE_ROOT を使用する

CMAKE_ROOT は、CMake の現在の作業ディレクトリを表す組み込み変数です。この変数を使用して、トップレベルディレクトリかどうかを判定することができます。

cmake_minimum_required(VERSION 3.0)

project(MyProject)

if(CMAKE_ROOT STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
  # トップレベルプロジェクトの場合のみ実行される処理
  message(STATUS "This is the top-level project directory.")
else()
  # サブディレクトリの場合
  message(STATUS "This is a subdirectory.")
endif()

親ディレクトリを確認する

現在のディレクトリの親ディレクトリを確認することで、トップレベルディレクトリかどうかを判定することができます。

cmake_minimum_required(VERSION 3.0)

project(MyProject)

if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt")
  # トップレベルプロジェクトの場合
  message(STATUS "This is the top-level project directory.")
else()
  # サブディレクトリの場合
  message(STATUS "This is a subdirectory.")
endif()

カスタム変数を使用する

プロジェクトのトップレベルかどうかを判定するカスタム変数を作成することができます。

cmake_minimum_required(VERSION 3.0)

project(MyProject)

set(PROJECT_TOP_LEVEL FALSE)

if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt")
  set(PROJECT_TOP_LEVEL TRUE)
endif()

if(PROJECT_TOP_LEVEL)
  # トップレベルプロジェクトの場合のみ実行される処理
  message(STATUS "This is the top-level project directory.")
else()
  # サブディレクトリの場合
  message(STATUS "This is a subdirectory.")
endif()

CMAKE_COMMAND を使用する

CMAKE_COMMAND 変数は、現在の CMake 実行コマンドを表す組み込み変数です。この変数を使用して、コマンドライン引数からトップレベルディレクトリかどうかを判定することができます。

cmake_minimum_required(VERSION 3.0)

project(MyProject)

string(FIND "${CMAKE_COMMAND}" "-DCMAKE_BUILD_ROOT=" BUILD_ROOT_DIR)

if(BUILD_ROOT_DIR STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
  # トップレベルプロジェクトの場合
  message(STATUS "This is the top-level project directory.")
else()
  # サブディレクトリの場合
  message(STATUS "This is a subdirectory.")
endif()

最適な代替方法の選択

上記の代替方法はそれぞれ長所と短所があります。状況に応じて最適な方法を選択する必要があります。

  • CMAKE_COMMAND を使用する: 高度な方法ですが、複雑なコマンドライン引数を使用している場合は、誤動作する可能性があります。
  • カスタム変数を使用する: 柔軟性が高い方法ですが、コードが煩雑になる可能性があります。
  • 親ディレクトリを確認する: どのバージョンの CMake でも使用できますが、CMakeLists.txt ファイルが存在しないサブディレクトリをトップレベルと誤認する可能性があります。
  • CMAKE_ROOT を使用する: 最もシンプルで分かりやすい方法ですが、CMake 3.0 以降でのみ使用可能です。
  • 具体的な方法は、プロジェクトの要件や環境に合わせて調整する必要があります。
  • 上記の代替方法はあくまでも例であり、状況に応じて様々な方法が考えられます。