CMake detect if project is top level

CMake can detect if a project is “top level” that is, NOT via FetchContent using PROJECT_IS_TOP_LEVEL and PROJECT_NAME_IS_TOP_LEVEL . For simplicity, we denote these variables in this article as “*_IS_TOP_LEVEL”.

Example use:

if(${PROJECT_NAME}_IS_TOP_LEVEL)
  message(STATUS "${PROJECT_NAME} directly building, not FetchContent")
endif()

For CMake < 3.21:

if(CMAKE_VERSION VERSION_LESS 3.21)
  get_property(not_top DIRECTORY PROPERTY PARENT_DIRECTORY)
  if(not_top)
    set(${PROJECT_NAME}_IS_TOP_LEVEL false)
  else()
    set(${PROJECT_NAME}_IS_TOP_LEVEL true)
  endif()
endif()

Caveats

Directory property PARENT_DIRECTORY and *_IS_TOP_LEVEL are NOT useful for detecting if the child project is being used as an ExternalProject.

These variables are based on the last “project()” command and so are not as universally useful as it first seems. For example, these variables do not work as expected when using ExternalProject. Even setting CMAKE_CACHE_ARGS of ExternalProject does not help, nor does cmake (1) command line options–the CMake-internal setting of *_IS_TOP_LEVEL overrides this attempt to set it. To workaround this, use an arbitrary auxiliary variable to detect if the project is top level.

Example:

Top-level CMakeLists.txt:

ExternalProject_Add(sub1
...
CMAKE_ARGS -DSUB1_IS_TOP:BOOL=false
)

ExternalProject_Add(sub2
...
CMAKE_ARGS -DSUB2_IS_TOP:BOOL=false
)

Subproject CMakeLists.txt

if(DEFINED SUB1_IS_TOP)
  set(SUB1_IS_TOP_LEVEL ${SUB1_IS_TOP})
endif()

Rather than try to directly workaround all the corner cases of *_IS_TOP_LEVEL, using this auxiliary variable allows the user to clearly force the intended behavior. This is useful when the subprojects and main project can build required ExternalProjects, and you want to only build the required ExternalProjects once.