Skip to content

Commit 162e1b8

Browse files
Add support for CONAN_INSTALL_BUILD_CONFIGURATIONS (#707)
* Add support for CONAN_INSTALL_BUILD_CONFIGURATIONS Let the user or project override the set of build types we execute the conan install command for. This is most useful for enabling projects to build only one configuration when using a multi-config generator, but it can also be used to install for multiple build types with a single-config generator. * use build_dir_multi fixture * test: fix build dir * fix logic around config override * fixes * Update conan_provider.cmake Co-authored-by: Craig Scott <craig.scott@crascit.com> * windows runtime tests: run on fresh build directory * Update docs to mention single-config restriction --------- Co-authored-by: Luis Caro Campos <3535649+jcar87@users.noreply.github.com>
1 parent b522d5a commit 162e1b8

File tree

3 files changed

+82
-13
lines changed

3 files changed

+82
-13
lines changed

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ cmake -B build -S . -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=[path-to-cmake-conan]/con
5050

5151
* Only the `CMakeDeps` generator is specified - for build settings that would otherwise be provided by `CMakeToolchain` (for example, the compiler itself or other global build settings) please invoke Conan separately as per [documentation](https://docs.conan.io/2/tutorial/consuming_packages/build_simple_cmake_project.html).
5252
* Currently this only works such that Conan can satisfy invocations to CMake's `find_package`. For dependencies that have logic outside of `find_package`, for example, by making direct calls to `find_program`, `find_library`, `find_path` or `find_file`, these may not work correctly.
53-
* When using a single-configuration CMake generator, you must specify a valid `CMAKE_BUILD_TYPE` (can't be left blank)
53+
* When using a single-configuration CMake generator, you must specify a valid `CMAKE_BUILD_TYPE` (can't be left blank). Alternatively, `CONAN_INSTALL_BUILD_CONFIGURATIONS` can be set to a non-empty list of build types (see next section).
5454
* Deriving Conan settings is currently only supported on the most common platforms with the most popular compilers.
5555

5656
### Customizing Conan profiles
@@ -68,10 +68,25 @@ If you need to customize the profile, you can do so by modifying the value of `C
6868
* `-DCONAN_BUILD_PROFILE="/path/to/profile"`: alternatively, provide a path to a profile file that may be anywhere in the filesystem.
6969
* `-DCONAN_HOST_PROFILE="default;custom"`: semi-colon separated list of profiles. A compound profile will be used (see [docs](https://docs.conan.io/2.0/reference/commands/install.html#profiles-settings-options-conf)) - compunded from left to right, where right has the highest priority.
7070

71+
Any `build_type` specified by a host profile is ignored.
72+
The `build_type` is handled separately, and the default behavior depends on the CMake generator used:
73+
* For single-configuration generators, `conan install` is executed once with `build_type` set to the `CMAKE_BUILD_TYPE`. The `CMAKE_BUILD_TYPE` must not be empty.
74+
* For multi-configuration generators, `conan install` is executed twice: once with `build_type` set to `Release`, and a second time with `build_type` set to `Debug`.
75+
76+
You can override the default build type(s) by setting the `CONAN_INSTALL_BUILD_CONFIGURATIONS` CMake variable to a list of build types.
77+
`conan install` will then be invoked once for each build type.
78+
This can be used with both single- and multi-configuration generators, although currently only one build type can be specified for single-configuration generators.
79+
For example:
80+
* `-DCONAN_INSTALL_BUILD_CONFIGURATIONS=Release;Debug`: execute `conan install` for both `Release` and `Debug` build types with a multi-configuration generator.
81+
* `-DCONAN_INSTALL_BUILD_CONFIGURATIONS=Release`: execute `conan install` once for just the `Release` build type, applicable for both single- and multi-configuration generators.
82+
7183
### Customizing the invocation of Conan install
7284
The CMake-Conan dependency provider will autodetect and pass the profile information as described above. If the `conan install` command invocation needs to be customized further, the `CONAN_INSTALL_ARGS` variable can be used.
7385
* By default, `CONAN_INSTALL_ARGS` is initialised to pass `--build=missing`. If you customize this variable, please be aware that Conan will revert to its default behaviour unless you specify the `--build` flag.
74-
* Two arguments are reserved to the dependency provider implementation and must not be set: the path to a `conanfile.txt|.py`, and the output format (`--format`).
86+
* Some arguments are reserved for the dependency provider implementation and must not be set in `CONAN_INSTALL_ARGS`:
87+
* The path to a `conanfile.txt|.py`.
88+
* The output format (`--format`).
89+
* The build type setting (`-s build_type=...`).
7590
* Values are semi-colon separated, e.g. `--build=never;--update;--lockfile-out=''`
7691

7792

conan_provider.cmake

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ endfunction()
463463
function(conan_install)
464464
set(conan_output_folder ${CMAKE_BINARY_DIR}/conan)
465465
# Invoke "conan install" with the provided arguments
466-
set(conan_args ${conan_args} -of=${conan_output_folder})
466+
set(conan_args -of=${conan_output_folder})
467467
message(STATUS "CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${conan_args} ${ARGN}")
468468

469469

@@ -591,18 +591,42 @@ macro(conan_provide_dependency method package_name)
591591
endif()
592592
set(generator "-g;CMakeDeps")
593593
endif()
594+
594595
get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
595-
if(NOT _multiconfig_generator)
596-
message(STATUS "CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}")
597-
conan_install(${_host_profile_flags} ${_build_profile_flags} ${CONAN_INSTALL_ARGS} ${generator})
596+
597+
if(DEFINED CONAN_INSTALL_BUILD_CONFIGURATIONS)
598+
# Configurations are specified by the project or user
599+
set(_build_configs "${CONAN_INSTALL_BUILD_CONFIGURATIONS}")
600+
list(LENGTH _build_configs _build_configs_length)
601+
if(NOT _multiconfig_generator AND _build_configs_length GREATER 1)
602+
message(FATAL_ERROR "cmake-conan: when using a single-config CMake generator, "
603+
"please only specify a single configuration in CONAN_INSTALL_BUILD_CONFIGURATIONS")
604+
endif()
605+
unset(_build_configs_length)
598606
else()
599-
message(STATUS "CMake-Conan: Installing both Debug and Release")
600-
conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release ${CONAN_INSTALL_ARGS} ${generator})
601-
conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug ${CONAN_INSTALL_ARGS} ${generator})
607+
# No configuration overrides, provide sensible defaults
608+
if(_multiconfig_generator)
609+
set(_build_configs Release Debug)
610+
else()
611+
set(_build_configs ${CMAKE_BUILD_TYPE})
612+
endif()
613+
602614
endif()
615+
list(JOIN _build_configs ", " _build_configs_msg)
616+
message(STATUS "CMake-Conan: Installing configuration(s): ${_build_configs_msg}")
617+
foreach(_build_config IN LISTS _build_configs)
618+
set(_self_build_config "")
619+
if(NOT _multiconfig_generator AND NOT _build_config STREQUAL "${CMAKE_BUILD_TYPE}")
620+
set(_self_build_config -s &:build_type=${CMAKE_BUILD_TYPE})
621+
endif()
622+
conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=${_build_config} ${_self_build_config} ${CONAN_INSTALL_ARGS} ${generator})
623+
endforeach()
624+
unset(_self_build_config)
625+
unset(_multiconfig_generator)
626+
unset(_build_configs)
627+
unset(_build_configs_msg)
603628
unset(_host_profile_flags)
604629
unset(_build_profile_flags)
605-
unset(_multiconfig_generator)
606630
unset(_conan_install_success)
607631
else()
608632
message(STATUS "CMake-Conan: find_package(${ARGV1}) found, 'conan install' already ran")

tests/test_smoke.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@
3636
def run(cmd, check=True):
3737
subprocess.run(cmd, shell=True, check=check)
3838

39+
def clear_folder_contents(folder_path):
40+
if not os.path.isdir(folder_path):
41+
raise ValueError(f"{folder_path} is not a valid directory.")
42+
for entry in os.listdir(folder_path):
43+
entry_path = os.path.join(folder_path, entry)
44+
if os.path.isfile(entry_path) or os.path.islink(entry_path):
45+
os.unlink(entry_path)
46+
elif os.path.isdir(entry_path):
47+
shutil.rmtree(entry_path)
3948

4049
@pytest.fixture(scope="session")
4150
def conan_home_dir(tmp_path_factory):
@@ -172,7 +181,7 @@ def test_single_config_only_one_configuration_installed(self, capfd):
172181
out, _ = capfd.readouterr()
173182
assert all(expected in out for expected in expected_conan_install_outputs)
174183
assert "Overriding config types" in out
175-
assert "CMake-Conan: Installing single configuration Release" in out
184+
assert "CMake-Conan: Installing configuration(s): Release\n" in out
176185
run("cmake --build .")
177186
out, _ = capfd.readouterr()
178187
assert all(expected not in out for expected in expected_conan_install_outputs)
@@ -183,6 +192,25 @@ def test_single_config_only_one_configuration_installed(self, capfd):
183192
expected_output = [f.format(config="Release") for f in expected_app_outputs]
184193
assert all(expected in out for expected in expected_output)
185194

195+
def test_single_config_override_install_config(self, capfd):
196+
"Ensure that CONAN_INSTALL_BUILD_CONFIGURATIONS is honored for single-config generators"
197+
generator = "-GNinja" if platform.system() == "Windows" else ""
198+
run(f'cmake -S {self.source_dir} -B {self.binary_dir} -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={conan_provider} {generator} -DCMAKE_BUILD_TYPE=RelWithDebInfo "-DCONAN_INSTALL_BUILD_CONFIGURATIONS=Release"')
199+
out, _ = capfd.readouterr()
200+
assert all(expected in out for expected in expected_conan_install_outputs)
201+
assert "CMake-Conan: Installing configuration(s): Release\n" in out
202+
# We don't need to do a build, just running CMake configure is enough to verify the config selection
203+
204+
@pytest.mark.usefixtures("build_dir_multi")
205+
def test_multi_config_override_install_config(self, capfd):
206+
"Ensure that CONAN_INSTALL_BUILD_CONFIGURATIONS is honored for multi-config generators"
207+
generator = "-G'Ninja Multi-Config'" if platform.system() != "Windows" else ""
208+
run(f'cmake -S {self.source_dir} -B {self.binary_dir_multi} -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={conan_provider} {generator} -DCONAN_INSTALL_BUILD_CONFIGURATIONS=Release')
209+
out, _ = capfd.readouterr()
210+
assert all(expected in out for expected in expected_conan_install_outputs)
211+
assert "CMake-Conan: Installing configuration(s): Release\n" in out
212+
# We don't need to do a build, just running CMake configure is enough to verify the config selection
213+
186214
@unix
187215
def test_reconfigure_on_conanfile_changes(self, capfd):
188216
"A conanfile change triggers conan install"
@@ -202,7 +230,8 @@ def test_reconfigure_on_conanfile_changes(self, capfd):
202230
"MultiThreaded$<$<CONFIG:Debug>:Debug>DLL",
203231
"MultiThreaded", "MultiThreadedDebugDLL"])
204232
def test_msvc_runtime_multiconfig(self, capfd, msvc_runtime):
205-
msvc_runtime_flag = f'-DCMAKE_MSVC_RUNTIME_LIBRARY="{msvc_runtime}"'
233+
msvc_runtime_flag = f'-DCMAKE_MSVC_RUNTIME_LIBRARY="{msvc_runtime}"'
234+
clear_folder_contents(self.binary_dir_multi)
206235
run(f"cmake -S {self.source_dir} -B {self.binary_dir_multi} -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={conan_provider} {msvc_runtime_flag}")
207236
out, _ = capfd.readouterr()
208237
assert all(expected in out for expected in expected_conan_install_outputs)
@@ -227,7 +256,8 @@ def test_msvc_runtime_multiconfig(self, capfd, msvc_runtime):
227256
"MultiThreaded$<$<CONFIG:Debug>:Debug>DLL",
228257
"MultiThreaded", "MultiThreadedDebugDLL"])
229258
def test_msvc_runtime_singleconfig(self, capfd, config, msvc_runtime):
230-
msvc_runtime_flag = f'-DCMAKE_MSVC_RUNTIME_LIBRARY="{msvc_runtime}"'
259+
msvc_runtime_flag = f'-DCMAKE_MSVC_RUNTIME_LIBRARY="{msvc_runtime}"'
260+
clear_folder_contents(self.binary_dir)
231261
run(f"cmake -S {self.source_dir} -B {self.binary_dir} -GNinja -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={conan_provider} -DCMAKE_BUILD_TYPE={config} {msvc_runtime_flag} -GNinja")
232262
out, _ = capfd.readouterr()
233263
assert all(expected in out for expected in expected_conan_install_outputs)

0 commit comments

Comments
 (0)