Skip to content

CMake

Debuging

  • Debug output: -LAH --debug-output --trace-expand
  • Variable changes in cmake: variable_watch(CMAKE_INSTALL_LIBDIR)

Dependency graph — [1]:

Option 1:

set_property(GLOBAL PROPERTY GLOBAL_DEPENDS_DEBUG_MODE 1)

Option 2:

cmake --graphviz=test.graph 
dotty test.graph

Main commands

Create proxy targets

Src.

find_package (libjpeg)
if (NOT TARGET libjpeg::libjpeg)
    find_package (JPEG REQUIRED)
    add_library (libjpeg::libjpeg INTERFACE IMPORTED)
    target_link_libraries (libjpeg::libjpeg PUBLIC JPEG::JPEG)
    message (STATUS "using system provided JPEG library")
else()
    message (STATUS "using conan provided JPEG library")
endif()

target_link_libraries (your_project_target PRIVATE libjpeg::libjpeg)

CMake for monorepos

The problem

Sometimes the default CMake approach is too monolithic. This is especially relevant in case of monorepos (which tend to be large and include sub-project of multiple teams, which you do not want to build at all).

  • For example, with the default cmake, given a monorepo, I cannot build a specific (single) sub-project. I can only build the entire repo.
  • Another (even more important) consequence is that the decision as to whether to add add_subdirectory(dependencyA) in the given CMakeLists.txt or not, is not local. I know that my target depends on dependencyA, but I do not know (locally, in the given CMakeLists.txt) whether it has already been included or not by a parent CMakeLists.txt.
  • Finally, with the default cmake approach I have to remember both: (1) target name (to specify it in target_link_libraries()) and (2) target path (to specify it in add_subdirectory()). Alternatively, we could auto-generate target names based on its path, and require to remember only paths.

The goal

The goal is to be able to write CMakeLists in this way:

  • Adding an executable:

    my/super/project/se2/CMakeLists.txt
    1
    2
    3
    add_exec(se2 main.cpp)
    depends_upon(nested/util)    # <-- Just list dependencies of the executable, regardless of
    depends_upon(my/super/lib)   #     whether they were included by someone else or not
    
  • Adding a library:

    my/super/lib/CMakeLists.txt
    add_static_library(pci.h pci.cc)   # <-- no need to come up with library name
    depends_upon(nested/util)
    
  • And configure in this way:

    cmake root/cmake/dir -DDIRS_TO_BUILD="my/super/project/se2"  # <-- specify only one sub-project to build
    

A prototype

Implementation of the functions is pretty straighforward — we just need:

  • to maintain a list of processed directories (processed_paths)
  • and to introduce our own depends_upon(directory) that will add_subdirectory(directory) only if it has not yet been added in the processed_paths:
root CMakeLists.txt
set(processed_paths "" CACHE INTERNAL "")

macro(set_if_not_set variable)
   if(NOT DEFINED ${variable})
      set(${variable} ${ARGN})
   endif()
endmacro()


function(get_default_target_name ret)
   file(RELATIVE_PATH relative_path ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
   string(REPLACE "/" "-" dashed_name ${relative_path})
   set(${ret} "${dashed_name}" PARENT_SCOPE)
endfunction()


macro(add_static_library)
   get_default_target_name(default_target_name)
   set_if_not_set(current_target ${default_target_name})
   message("SubProjectBuilder: add_static_library: Adding STATIC library ${current_target}, with files: ${ARGV}")
   add_library(${current_target} STATIC ${ARGV})
endmacro()


macro(add_exec name)
   set(current_target ${name})
   message("SubProjectBuilder: add_exec: Adding executable ${ARGV}")
   add_executable(${ARGV})
endmacro()


function(add_subdirectory_if_not_added name)
   # message("SubProjectBuilder: add_subdirectory_if_not_added: ARGV: ${ARGV}, processed_paths: ${processed_paths}")
   string(REPLACE "/" "-" dashed_name ${name})
   if("${processed_paths}" MATCHES "X${dashed_name}X")
      # message("SubProjectBuilder: add_subdirectory_if_not_added: dashed_name: ${dashed_name} found in the list of processed paths, ignoring it...")
      return()
   endif()

   # message("SubProjectBuilder: add_subdirectory_if_not_added: dashed_name: ${dashed_name} was not found in the list of processed paths, adding it...")
   set(local_processed_paths ${processed_paths})
   list(APPEND local_processed_paths "X${dashed_name}X")
   # message("SubProjectBuilder: add_subdirectory_if_not_added: local_processed_paths: ${local_processed_paths}")
   set(processed_paths "${local_processed_paths}" CACHE INTERNAL "")

   set(backup_current_target ${current_target})
   unset(current_target)
   message("SubProjectBuilder: add_subdirectory_if_not_added: recursing to: ${name}")
   add_subdirectory(${PROJECT_SOURCE_DIR}/${name} ${PROJECT_BINARY_DIR}/${name})
   set(current_target ${backup_current_target})
endfunction()

function(depends_upon paths)
   # message("SubProjectBuilder: depends_upon: name: ${current_target}, path: ${path}")
   foreach(path IN LISTS ARGV)
      add_subdirectory_if_not_added(${path})
      string(REPLACE "/" "-" dashed_name ${path})
      target_link_libraries(${current_target} ${dashed_name})
   endforeach()
endfunction()



# =================================================================================================


include_directories(.)

foreach(target_name IN LISTS DIRS_TO_BUILD)
   add_subdirectory_if_not_added(${target_name})
endforeach()