# Mapping CMAKE\_BUILD\_TYPE and CMAKE\_CONFIGURATION\_TYPES between project subdirectories

**URL:** https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192
**Category:** Code
**Created:** [November 13, 2019, 1:11pm UTC](https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192 "2019-11-13T13:11:06Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![phcerdan](https://discourse.cmake.org/user_avatar/discourse.cmake.org/phcerdan/32/155_2.png) [@phcerdan](https://discourse.cmake.org/u/phcerdan)
#### Post date: [November 13, 2019, 1:11pm UTC](https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192/1 "2019-11-13T13:11:06Z")

</div>

Better to explain with a use case:  
ProjectThird uses non-standard CMAKE\_CONFIGURATION\_TYPES, ie: checked, release  
I want to include ProjectThird into my ProjectOwn, however ProjectOwn has the standard `CMAKE_CONFIGURATION_TYPES`: Release, RelWithDebInfo, Debug, …

How can I map the CMAKE\_CONFIGURATION\_TYPE from ProjectOwn, to CMAKE\_CONFIGURATION\_TYPES of ProjectThird?

- with `FetchContent`:

```cmake
  # Fetch ProjectThird (CMake phcerdan branch)
  include(FetchContent)
  FetchContent_Declare(
    ProjectThird
    GIT_REPOSITORY https://github.com/phcerdan/ProjectThird
    GIT_TAG cmake_for_easier_integration
  )
  FetchContent_GetProperties(ProjectThird)
  if(NOT ProjectThird_POPULATED)
    set(original_cmake_build_type ${CMAKE_BUILD_TYPE})
    # Try to always use release for ProjectThird, no matter what current config/build_type
    set(CMAKE_BUILD_TYPE "release" CACHE INTERNAL "")
    FetchContent_Populate(ProjectThird)
    add_subdirectory(${ProjectThird_SOURCE_DIR} ${ProjectThird_BINARY_DIR})
    # Restore CMAKE_BUILD_TYPE to the original of the current project.
    set(CMAKE_BUILD_TYPE "${original_cmake_build_type}" CACHE INTERNAL "")
     # Not Working. Not able to set CMAKE_BUILD_TYPE only for the subdirectory...
  endif()

```

I am starting to think that the only way to handle this third party is with `ExternalProject` if there is not a way to create “CACHE” variables with a scope limited to subdirectories.

Have you faced this problem before? What would you recommend?

---

<div class="post-metadata">

### Author: ![phcerdan](https://discourse.cmake.org/user_avatar/discourse.cmake.org/phcerdan/32/155_2.png) [@phcerdan](https://discourse.cmake.org/u/phcerdan)
#### Post date: [November 13, 2019, 1:34pm UTC](https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192/2 "2019-11-13T13:34:19Z")

</div>

I am guessing something like the following work (WIP) proposed by @craig.scott should work:  
[https://gitlab.kitware.com/cmake/cmake/issues/18831](https://gitlab.kitware.com/cmake/cmake/issues/18831)

```cmake
# WIP from https://gitlab.kitware.com/cmake/cmake/issues/18831
FetchContent_GetProperties(boost)
if(NOT boost_POPULATED)
    FetchContent_Populate(boost)

    # The following needs to be run after initial checkout or
    # after the dependency details of boost are changed.
    # The following option is provided to prevent having to
    # keep reconfiguring and rerunning the boost build each
    # time CMake runs once it has been successfully performed
    # for the version specified.
    option(ENABLE_BOOST_BUILD "Enable reconfiguring and rerunning the boost build" ON)
    if(ENABLE_BOOST_BUILD)
        # This file comes from the following location:
        # https://github.com/pfultz2/cget/blob/master/cget/cmake/boost.cmake
        configure_file(boost.cmake
                       ${boost_SOURCE_DIR}/CMakeLists.txt
                       COPYONLY
        )

        unset(generatorArgs)
        set(cacheArgs
            "-DCMAKE_INSTALL_PREFIX:STRING=${boost_BINARY_DIR}/install"
            "-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=YES"
            "-DBUILD_SHARED_LIBS:BOOL=${BUILD_SHARED_LIBS}"
        )
        if(CMAKE_TOOLCHAIN_FILE)
            list(APPEND cacheArgs "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_TOOLCHAIN_FILE}")
        else()
            list(APPEND cacheArgs "-DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}"
                                  "-DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}"
            )
        endif()

        get_property(isMulti GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
        if(NOT isMulti)
            list(APPEND cacheArgs "-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}")
        endif()

        if(CMAKE_GENERATOR_PLATFORM)
            list(APPEND generatorArgs
                 --build-generator-platform "${CMAKE_GENERATOR_PLATFORM}"
            )
        endif()
        if(CMAKE_GENERATOR_TOOLSET)
            list(APPEND generatorArgs
                 --build-generator-toolset "${CMAKE_GENERATOR_TOOLSET}"
            )
        endif()

        # Assume parent dir has set BOOST_LIBS to a list of the boost modules
        # we want build and made available
        foreach(lib IN LISTS BOOST_LIBS)
            list(APPEND cacheArgs "-DBOOST_WITH_${lib}:STRING=--with-${lib}")
        endforeach()

        message(STATUS "Configuring and building boost immediately")
        execute_process(
            COMMAND ${CMAKE_CTEST_COMMAND}
                    --build-and-test ${boost_SOURCE_DIR} ${boost_BINARY_DIR}
                    --build-generator ${CMAKE_GENERATOR} ${generatorArgs}
                    --build-target install
                    --build-noclean
                    --build-options ${cacheArgs}
            WORKING_DIRECTORY ${boost_SOURCE_DIR}
            OUTPUT_FILE ${boost_BINARY_DIR}/build_output.log
            ERROR_FILE ${boost_BINARY_DIR}/build_output.log
            RESULT_VARIABLE result
        )
        message(STATUS "boost build complete")
        if(result)
            message(FATAL_ERROR "Failed boost build, see build log at:\n"
                " ${boost_BINARY_DIR}/build_output.log")
        endif()

    endif()
    set(CMAKE_PREFIX_PATH ${boost_BINARY_DIR}/install)
endif()

# Confirm we can find Boost. If this is the first time we've
# tried to find it in this build dir, this call will force the
# location of each Boost library to be saved in the cache, so
# later calls elsewhere in the project will find the same ones.
find_package(Boost QUIET REQUIRED COMPONENTS ${BOOST_LIBS})

```

Instead of `add_subdirectory` this solution uses `execute_process` where we can pass and set CACHE variables explicitly.  
@craig.scott, do you think this is a good solution for my case of different `CMAKE_CONFIGURATION_TYPES` between projects, or you think there a simpler solution? Thanks!

---

<div class="post-metadata">

### Author: ![craig.scott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/craig.scott/32/20_2.png) [@craig.scott](https://discourse.cmake.org/u/craig.scott)
#### Post date: [November 14, 2019, 2:10am UTC](https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192/3 "2019-11-14T02:10:04Z")

</div>

I will reply in parts to keep the discussion threads a bit more manageable. I’ll reply here first to address some points in the example, see the other reply coming later for more direct discussion of your query.

The first thing to clear up is that you should think of `CMAKE_BUILD_TYPE` and `CMAKE_CONFIGURATION_TYPES` as separate things and only one of the two is relevant for a given build. For single configuration generators (e.g. Unix Makefiles or Ninja), only `CMAKE_BUILD_TYPE` is relevant. `CMAKE_CONFIGURATION_TYPES` is ignored and indeed should be left unset. For multi-config generators (e.g. Xcode and Visual Studio), only `CMAKE_CONFIGURATION_TYPES` is relevant and `CMAKE_BUILD_TYPE` will be ignored.

In your situation, it sounds like you are only having difficulties with `CMAKE_CONFIGURATION_TYPES`, so you should be leaving `CMAKE_BUILD_TYPE` alone. Even if this were not the case, the code in your example would result in `CMAKE_BUILD_TYPE` getting hidden in the CMake cache because it changes the variable type to `INTERNAL`. This should never be done, it is meant to be under developer control. You should also not try to mix different values of `CMAKE_BUILD_TYPE` within one build, it will likely result in inconsistencies in the build, if not build failure.

---

<div class="post-metadata">

### Author: ![craig.scott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/craig.scott/32/20_2.png) [@craig.scott](https://discourse.cmake.org/u/craig.scott)
#### Post date: [November 14, 2019, 2:21am UTC](https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192/4 "2019-11-14T02:21:37Z")

</div>

`CMAKE_CONFIGURATION_TYPES` represents a set of defined build types that are global to the whole build. You can’t have different sets of types for different parts of your build. It maps to a single combo box in Visual Studio and to something similar in the schemes used in Xcode. The user selects one of these values for the entire build.

In your case, you can potentially discard the configuration types defined by the third party project, or you could merge them into those expected by the main project. It depends on what the third party project tries to do with this information. If you really do need to allow the third party project to have its own set of configuration types, then you have to isolate it from the main build. That means doing its build off to the side with something like `ExternalProject_Add()` instead of incorporating it directly into the main build with `add_subdirectory()`.

The sample code of mine for boost that you referenced is somewhat of a special case. Boost is complex and it’s still a moving target in terms of what it defines, how libraries are named, how distributions package it up, etc. You want to leave as much handling those differences as you can to boost’s own build and perhaps CMake’s own FindBoost module. My sample code tries to do that but still is pretty complex and doesn’t necessarily work with all platforms/generators. If your third party project has more predictable build artefacts, then it may be simpler to use `ExternalProject_Add()` and manually create imported targets to represent the build artefacts you want to use from it. It’s not a path I normally recommend, but it can be made to work in some situations. A more traditional super build would probably be more advisable (where your main project would also become a sub-build brought in vial `ExternalProject_Add()`), but that comes with its own disadvantages. You will need to investigate and see which choices offer the set of compromises you are most happy with.

---

<div class="post-metadata">

### Author: ![phcerdan](https://discourse.cmake.org/user_avatar/discourse.cmake.org/phcerdan/32/155_2.png) [@phcerdan](https://discourse.cmake.org/u/phcerdan)
#### Post date: [November 14, 2019, 9:35am UTC](https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192/5 "2019-11-14T09:35:24Z")

</div>

Thanks @craig.scott for your great responses.

The differences of `CMAKE_BUILD_TYPE` and `CMAKE_CONFIGURATION_TYPES` are now clear.

> [@craig.scott](#):
>
> `CMAKE_CONFIGURATION_TYPES` represents a set of defined build types that are global to the whole build. You can’t have different sets of types for different parts of your build

This makes impossible to use `add_subdirectories` (when using `FetchContent` or `git submodules`) with projects with different `CMAKE_CONFIGURATION_TYPES`. Especially when that project has more complicated logic depending on the configuration.  
Can a map/dictionary be created in a project with non-default `CONFIGURATION_TYPES` to reconciliate it with the default types?

```cmake
# Config dictionary at top of project with type1, type2 (non-default) CONFIGURATION_TYPES
# to map default configs to this project.
Release -> type1
Debug -> type2
RelWithDebInfo -> type3
MinSizeRel-> type1 + some extra flags.

```

I am not familiar with CMake codebase, but I guess `CONFIGURATION` related variable with per project scope, `CONFIG_PROJECT`, `CMAKE_PROJECT_CONFIGURATION_TYPES` can help to achieve this mapping.

In my case, I would need to go the route of `ExternalProject`, or fork and change third-party codebase to make it compatible with the CMake defaults. The problem is that the code was open-sourced recently and has configurations internal to that company (used in their mono repos or whatever), so it’s going to make future changes challenging if I change it.

---

<div class="post-metadata">

### Author: ![craig.scott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/craig.scott/32/20_2.png) [@craig.scott](https://discourse.cmake.org/u/craig.scott)
#### Post date: [November 14, 2019, 10:39am UTC](https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192/6 "2019-11-14T10:39:24Z")

</div>

If you bring in the third party project with `ExternalProject_Add()`, you can map its configurations to yours using the [`MAP_IMPORTED_CONFIG_<CONFIG>`](https://cmake.org/cmake/help/latest/prop_tgt/MAP_IMPORTED_CONFIG_CONFIG.html) target property (you’d probably use the `CMAKE_MAP_IMPORTED_CONFIG_<CONFIG>` variable to do it globally for your build). It requires you to define imported targets for each of the targets you wanted to bring into your build from the third party project, but you would probably have to do that anyway.

Sorry if that’s all a bit brief, but hopefully it points you in the right direction for your own further investigations.

---

<div class="post-metadata">

### Author: ![phcerdan](https://discourse.cmake.org/user_avatar/discourse.cmake.org/phcerdan/32/155_2.png) [@phcerdan](https://discourse.cmake.org/u/phcerdan)
#### Post date: [November 14, 2019, 10:53am UTC](https://discourse.cmake.org/t/mapping-cmake-build-type-and-cmake-configuration-types-between-project-subdirectories/192/7 "2019-11-14T10:53:58Z")

</div>

Thanks @craig.scott, it helps, it would be great to extend that configuration mapping on imported targets to allow `add_subdirectories` to work.

I am posting what I am using right now, and it seems working. Kind of an hybrid approach, an external project built at configuration time (without `add_subdirectory`) where I can set the `CONFIGURATION_TYPE` of this project with a CACHE option in my own project. In case it helps anybody.

```cmake
option(FETCH_PROJECTTHIRD "Build project_thrid at configure time." ON)
if(FETCH_PROJECTTHIRD)
  # Fetch project_third
  include(FetchContent)
  FetchContent_Declare(
    project_third
    GIT_REPOSITORY https://github.com/phcerdan/project_third
    GIT_TAG cmake_for_easier_integration
  )
  FetchContent_GetProperties(project_third)
  if(NOT project_third_POPULATED)
    message(STATUS "Populating project_third...")
    FetchContent_Populate(project_third)
    message(STATUS "Configuring project_third...")
    execute_process(
      COMMAND ${CMAKE_COMMAND}
        -S ${project_third_SOURCE_DIR}
        -B ${project_third_BINARY_DIR}
        -DCMAKE_GENERATOR=${CMAKE_GENERATOR}
        -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
      WORKING_DIRECTORY ${project_third_BINARY_DIR}
      COMMAND_ECHO STDOUT
      # OUTPUT_FILE ${project_third_BINARY_DIR}/configure_output.log
      # ERROR_FILE ${project_third_BINARY_DIR}/configure_output.log
      RESULT_VARIABLE result_config
      )
    if(result_config)
        message(FATAL_ERROR "Failed project_third configuration")
        # see configuration log at:\n ${project_third_BINARY_DIR}/configure_output.log")
    endif()
    # Right now, project_third is always on release mode, but can be explicitly changed by user:
    set(project_third_CONFIG_TYPE "release" CACHE INTERNAL "Config/build type for project_third")
    message(STATUS "Building project_third... with CONFIG: ${project_third_CONFIG_TYPE}")
    execute_process(
      COMMAND ${CMAKE_COMMAND}
      --build ${project_third_BINARY_DIR}
      --config ${project_third_CONFIG_TYPE}
      WORKING_DIRECTORY ${project_third_BINARY_DIR}
      COMMAND_ECHO STDOUT
      # OUTPUT_FILE ${project_third_BINARY_DIR}/build_output.log
      # ERROR_FILE ${project_third_BINARY_DIR}/build_output.log
      RESULT_VARIABLE result_build
      )
    message(STATUS "project_third build complete")
    if(result_build)
        message(FATAL_ERROR "Failed project_third build")
        # see build log at:\n ${project_third_BINARY_DIR}/build_output.log")
    endif()
  endif()
  # create rule to install project_third when installing this project
  install (CODE "
  execute_process(
    COMMAND ${CMAKE_COMMAND}
    --build ${project_third_BINARY_DIR}
    --config ${project_third_CONFIG_TYPE}
    --target install
    WORKING_DIRECTORY ${project_third_BINARY_DIR}
    COMMAND_ECHO STDOUT
    )")
  # find_package works
  find_package(project_third REQUIRED
    PATHS ${project_third_BINARY_DIR}
    NO_DEFAULT_PATH
    )
endif()

```
