2
0
mirror of https://github.com/boostorg/cmake.git synced 2026-01-19 04:02:15 +00:00

Merge pull request #86 from Flamefire/separate-test-deps

Don't add all libraries when BOOST_INCLUDE_LIBRARIES is set
This commit is contained in:
Peter Dimov
2025-09-18 19:11:30 +03:00
committed by GitHub
3 changed files with 196 additions and 87 deletions

View File

@@ -407,22 +407,32 @@ jobs:
cp -r $GITHUB_WORKSPACE/* tools/cmake
- name: Configure each library independently
run: |
set -o pipefail
error=""
failed_libs=""
failed_outputs=()
cd ../boost-root
for cml in libs/*/CMakeLists.txt; do
lib=$(dirname "${cml#*libs/}")
echo "====================================================================="
echo "Building $lib"
echo "====================================================================="
cmake -DBUILD_TESTING=${{matrix.enable_test}} -DBOOST_INCLUDE_LIBRARIES=$lib -B "__build_$lib" . 2>&1 | tee /tmp/config.log || error+=" $lib"
error=0
out=$(cmake -DBUILD_TESTING=${{matrix.enable_test}} -DBOOST_INCLUDE_LIBRARIES=$lib -DBoost_DEBUG=ON -B "__build_$lib" . 2>&1) || error=1
echo "$out"
echo; echo; echo
if grep -F "BOOST_INCLUDE_LIBRARIES has not been found" "/tmp/config.log"; then
error+=" $lib"
[[ "$out" != *"BOOST_INCLUDE_LIBRARIES has not been found"* ]] || error=1
[[ "$out" != *"CMake Error"* ]] || error=1
if ((error==1)); then
failed_libs+=" $lib"
failed_outputs+=(
"====================================================================="
"Output of $lib"
"$out"
)
fi
done
if [[ -n $error ]]; then
echo "Failed libraries: $error"
if [[ -n $failed_libs ]]; then
echo "Failed libraries: $failed_libs"
printf '%s\n' "${failed_outputs[@]}"
exit 1
fi

View File

@@ -13,7 +13,7 @@ name of the repository (or the directory name).
For example, a `CMakeLists.txt` file for Boost.Core can be generated with
`boostdep --cmake core`, and the result will be, as of this writing,
```
```cmake
# Generated by `boostdep --cmake core`
# Copyright 2020 Peter Dimov
# Distributed under the Boost Software License, Version 1.0.
@@ -62,7 +62,7 @@ generated output provides a useful starting point. Its contents are explained
below.
### Version Requirement
```
```cmake
cmake_minimum_required(VERSION 3.5...3.16)
```
@@ -72,7 +72,7 @@ error at configure time, and inability to proceed with building.
In addition, this number changes the behavior of newer CMake versions to
attempt to be compatible with the stated version. If this only said
```
```cmake
cmake_minimum_required(VERSION 3.5)
```
a newer version of CMake would have emulated version 3.5. The additional
@@ -80,7 +80,7 @@ a newer version of CMake would have emulated version 3.5. The additional
This is typically the latest version of CMake with which the `CMakeLists.txt`
file has been tested. If you make changes to the file for other reasons, you
may want to update the directive to, say,
```
```cmake
cmake_minimum_required(VERSION 3.5...3.20)
```
You should avoid increasing the minimal CMake requirement above the Boost
@@ -92,7 +92,7 @@ your library from the build with `-DBOOST_EXCLUDE_LIBRARIES`, which is not
an ideal user experience.
### Project Declaration
```
```cmake
project(boost_core VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
```
@@ -111,7 +111,7 @@ Boost version (such as `1.77.0`.)
If your library is included directly in a user project with
`add_subdirectory`, `BOOST_SUPERPROJECT_VERSION` will not be set and
the project version will be empty, as if it weren't given:
```
```cmake
project(boost_core LANGUAGES CXX)
```
This is usually what one wants. Since manually maintaining a version
@@ -125,7 +125,7 @@ C++. C is only needed if the library has C source files, which a
header-only library does not have.
### Library Target Declaration
```
```cmake
add_library(boost_core INTERFACE)
```
@@ -133,7 +133,7 @@ The first `add_library` declares the library target, which by convention
is `boost_libname`, same as the project name. `INTERFACE` means that
this library is header-only and requires no building.
```
```cmake
add_library(Boost::core ALIAS boost_core)
```
@@ -145,7 +145,7 @@ alphanumeric `boost_core` may refer to either a target or to a library
on disk named f.ex. `libboost_core.so`.
### Include Directory Declaration
```
```cmake
target_include_directories(boost_core INTERFACE include)
```
@@ -156,18 +156,18 @@ location of the current `CMakeLists.txt` file.)
If you are familiar with CMake, your first impulse would be to declare this
line wrong, and replace it with
```
```cmake
target_include_directories(boost_core INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include)
```
or
```
```cmake
target_include_directories(boost_core INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include
$<INSTALL_INTERFACE:include>)
```
or perhaps
```
```cmake
target_include_directories(boost_core INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
@@ -180,7 +180,7 @@ the value of the include path to something like that last alternative
`BOOST_INSTALL_LAYOUT` and `BOOST_INSTALL_INCLUDE_SUBDIR`.)
### Dependencies
```
```cmake
target_link_libraries(boost_core
INTERFACE
Boost::assert
@@ -213,18 +213,48 @@ include path because it doesn't require any compilation. This is what the
`INTERFACE` keyword means - it sets the "usage requirements" of the target,
which are propagated upwards to its users.)
The exact form of the directive, with each `Boost::libname` target on its
own line, with nothing else, is significant. (In particular, the closing
parenthesis should not be placed on the same line as the last target.)
This requirement is imposed by the behavior of the user-settable
`BOOST_INCLUDE_LIBRARIES` option of the superproject, which requests only
the listed libraries and their dependencies to be configured, built, and/or
installed. To determine the dependencies, a simple-minded parser scans the
`CMakeLists.txt` files, looking for strings matching `Boost::libname` on
their own line.
Note that the exact form of the directive, with each `Boost::libname` target on
its own line, is no longer necessary after Boost 1.89.
You can as well put them on a singe line:
`target_link_libraries(boost_core INTERFACE Boost::assert Boost::config Boost::static_assert)`
This dependency specification influences the behavior of the user-settable
`BOOST_INCLUDE_LIBRARIES` option of the superproject, which requests only the
listed libraries and their dependencies to be configured, built, and/or
installed.
To determine the dependencies, a simple parser scans the `CMakeLists.txt`
files, looking for strings matching `Boost::libname`, excluding the name of the
current library and those appearing in comments. Additionally, you can use
pragmas to influence the scanner:
```cmake
# Ignored by parser if in library folder "core"
add_library(Boost::core ALIAS boost_core)
# Parser adds "Boost::assert" "Boost::dummy"
target_link_libraries(boost_core INTERFACE Boost::assert Boost::dummy
# Only "Boost::config added for those 2 lines
Boost::config # Boost::static_assert
# Boost::ignored
)
# Current dependencies: Boost::assert, Boost::dummy Boost::config
...
# Pragmas using Boost-Include, Boost-Exclude with or without colons and/or whitespace
# Boost-Include: Boost::filesystem
# Boost-Exclude Boost::dummy
# Final dependencies: Boost::assert, Boost::config, Boost::filesystem
```
This is useful if the parser misdetects a dependency (please open an issue)
or e.g. for optional dependencies.
Dependencies in `test/CMakeLists.txt` (and subfolders) are handled in the same
way, except their tests won't be build and those libraries won't be installed
unless they are dependencies in the root CMakeLists.txt of the current library
or any (transitive) dependency of those.
### Testing Support
```
```cmake
if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt")
add_subdirectory(test)
@@ -251,7 +281,7 @@ or desired.
If your library has a `test/CMakeLists.txt` file that is not intended to be
used from the Boost superproject, and is incompatible with it, replace this
block with either
```
```cmake
if(BUILD_TESTING AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
add_subdirectory(test)
@@ -261,7 +291,7 @@ endif()
when your test suite is only intended to be used when your library is the
root project (that's usually the case, so this option is the recommended one),
or
```
```cmake
if(BUILD_TESTING AND NOT BOOST_SUPERPROJECT_VERSION)
add_subdirectory(test)
@@ -289,7 +319,7 @@ Let the superproject handle it.
If your library needs C++11 or above, you can declare this requirement by
adding the following directive:
```
```cmake
target_compile_features(boost_libname INTERFACE cxx_std_11)
```
(use `cxx_std_14` for C++14, `cxx_std_17` for C++17, and so on.)
@@ -313,7 +343,7 @@ Many library authors who use CMake, however, add development-centric
functionality to their `CMakeLists.txt` file; you might already have. In this
case, try to keep the `CMakeLists.txt` portions described so far as close to
unchanged as possible, and at the end, add a section guarded with
```
```cmake
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
# Functionality enabled only when we're the root project
@@ -333,7 +363,7 @@ Even if your library requires compilation, you can still use
`boostdep --cmake libname` at least as a starting point. We'll take
Timer as an example, with the output of `boostdep --cmake timer` given
below:
```
```cmake
# Generated by `boostdep --cmake timer`
# Copyright 2020 Peter Dimov
# Distributed under the Boost Software License, Version 1.0.
@@ -386,7 +416,7 @@ We won't be repeating the explanations of the sections that match the
header-only case, and will only focus on the differences.
### Source Files
```
```cmake
add_library(boost_timer
src/auto_timers_construction.cpp
src/cpu_timer.cpp
@@ -400,7 +430,7 @@ the contents of your `src` subdirectory (but ignores any subdirectories.)
Since Timer is a simple library, this works as-is. Many compiled libraries
however might require adjusting the source file list, or choosing it based
on the platform. For example, Thread needs something like
```
```cmake
if(BOOST_THREAD_THREADAPI STREQUAL win32)
set(THREAD_SOURCES
@@ -428,7 +458,7 @@ The logic for choosing the source files is already spelled out in your
If your library has C source files, you'll need to also enable C as a
language in your project declaration:
```
```cmake
project(boost_container VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES C CXX)
```
although `boostdep` might already have done so for you.
@@ -438,7 +468,7 @@ or a shared library depending on whether `BUILD_SHARED_LIBS` is set to `ON`
or `OFF`. This is idiomatic CMake behavior and is what we want.
### Directive Scope
```
```cmake
target_include_directories(boost_timer PUBLIC include)
```
@@ -446,7 +476,7 @@ The only difference with the header-only case is the use of `PUBLIC` instead
of `INTERFACE`. `PUBLIC` applies to both the library and its dependents; in
`b2` terms it declares both a requirement and a usage-requirement.
```
```cmake
target_link_libraries(boost_timer
PUBLIC
Boost::config
@@ -467,7 +497,7 @@ subdirectory in the `PUBLIC` section, and those referred to from the `src`
subdirectory in the `PRIVATE` section.
### Compile Definitions
```
```cmake
target_compile_definitions(boost_timer
PUBLIC BOOST_TIMER_NO_LIB
PRIVATE BOOST_TIMER_SOURCE
@@ -480,7 +510,7 @@ to disable autolink and `BOOST_TIMER_SOURCE` when compiling the library to
properly declare exported functions as exported (as opposed to imported,
which will be the case when using the library.)
```
```cmake
if(BUILD_SHARED_LIBS)
target_compile_definitions(boost_timer PUBLIC BOOST_TIMER_DYN_LINK)
else()
@@ -508,7 +538,7 @@ library targets, `boost_serialization` and `boost_wserialization`, and the
procedure to install them entails adding
[the following section](https://github.com/boostorg/serialization/blob/337b3fbc7c4648d6f95f863546b9482500c8dec5/CMakeLists.txt#L116-L118)
to `CMakeLists.txt`:
```
```cmake
if(BOOST_SUPERPROJECT_VERSION AND NOT CMAKE_VERSION VERSION_LESS 3.13)
boost_install(TARGETS boost_serialization boost_wserialization
VERSION ${BOOST_SUPERPROJECT_VERSION} HEADER_DIRECTORY include)
@@ -528,7 +558,7 @@ than one library, see [Boost.Test](https://github.com/boostorg/test/blob/bce2d24
If your library uses multiple threads or threading primitives, you need to
add the following snippet to your `CMakeLists.txt` file:
```
```cmake
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
```
@@ -558,7 +588,7 @@ The recommended way to provide such optional functionality is to allow user
configuration with sensible defaults, as shown in the following example that
allows optional use of ZLib:
```
```cmake
find_package(ZLIB QUIET) # Look for ZLib
option(BOOST_MYLIB_ENABLE_ZLIB "Boost.MyLib: enable ZLib support" ${ZLIB_FOUND})
@@ -629,7 +659,7 @@ uses the library with `add_subdirectory`. Typically, the options people add
to their libraries are only relevant when the library is the root project.)
Definitely don't do this:
```
```cmake
option(BOOST_MYLIB_MYOPTION "" ON)
if(BOOST_MYLIB_MYOPTION AND NOT BOOST_SUPERPROJECT_VERSION)
@@ -642,7 +672,7 @@ endif()
This displays the option, but makes it do nothing. Instead, either put the
option declaration inside an `if`, or use
[`CMakeDependentOption`](https://cmake.org/cmake/help/latest/module/CMakeDependentOption.html):
```
```cmake
include(CMakeDependentOption)
cmake_dependent_option(BOOST_MYLIB_MYOPTION "" ON "NOT BOOST_SUPERPROJECT_VERSION" OFF)
@@ -694,7 +724,7 @@ project/library, such as `boost_mylib-mytarget`.
`add_test` does anything. Unless `BUILD_TESTING` is `ON`, to save time, you
should avoid creating any tests or targets on which they depend. Usually, this
translates to
```
```cmake
if(BUILD_TESTING)
add_subdirectory(test)
endif()
@@ -709,7 +739,7 @@ one would write in C++ `foo? "bar": "baz"`, one could write in CMake
Don't do this. It's not the same. Generator expressions are evaluated in the
generate phase, which happens after the configure phase. If you do
```
```cmake
target_compile_definitions(boost_mylib PUBLIC $<IF:$<BOOL:${BUILD_SHARED_LIBS}>,BOOST_MYLIB_DYN_LINK,BOOST_MYLINK_STATIC_LINK>)
```
(and assuming `BUILD_SHARED_LIBS` is `ON`), you're not setting the
@@ -719,7 +749,7 @@ to `$<IF:$<BOOL:ON>,BOOST_MYLIB_DYN_LINK,BOOST_MYLINK_STATIC_LINK>`.
Yes, it will still be evaluated to the right thing during generation, but it's
better to perform evaluations that only depend on configuration-time values at
configuration time and write the less "clever"
```
```cmake
if(BUILD_SHARED_LIBS)
target_compile_definitions(boost_mylib PUBLIC BOOST_MYLIB_DYN_LINK)
else()
@@ -736,7 +766,7 @@ Boost with CMake (and optionally, running the tests, if one has a few days
to spare).
The building procedure would generally involve issuing (from the Boost root)
```
```bash
mkdir __build
cd __build
cmake <configuration options> ..
@@ -748,7 +778,7 @@ configuration options in subdirectories of the "stage" directory, by default
`stage/lib` and `stage/bin`.
Subsequent installation would be performed with
```
```bash
cmake --build . --target install
```
assuming that `CMAKE_INSTALL_PREFIX` was set beforehand to the desired
@@ -760,19 +790,19 @@ and installation procedure would need to be performed twice, once with
`--config RelWithDebInfo`, as desired.)
Testing the entire Boost would be performed with
```
```bash
cmake -DBUILD_TESTING=ON ..
cmake --build . --target tests -j <threads>
ctest --output-on-failure -j <threads>
```
Again, when using the Visual Studio generator, this would be
```
```bash
cmake --build . --target tests -j <threads> --config Debug
ctest --output-on-failure -j <threads> -C Debug
```
resp.
```
```bash
cmake --build . --target tests -j <threads> --config Release
ctest --output-on-failure -j <threads> -C Release
```

View File

@@ -202,27 +202,76 @@ function(__boost_auto_install __boost_lib)
endif()
endfunction()
function(__boost_scan_dependencies lib var)
function(__boost_scan_dependencies lib var sub_folder)
# Libraries that define at least one library with a name like "<prefix>_"
set(prefix_names "asio" "dll" "fiber" "log" "regex" "stacktrace")
set(result "")
if(EXISTS "${BOOST_SUPERPROJECT_SOURCE_DIR}/libs/${lib}/CMakeLists.txt")
set(cml_files "${BOOST_SUPERPROJECT_SOURCE_DIR}/libs/${lib}")
if(sub_folder)
file(GLOB_RECURSE cml_files "${cml_files}/${sub_folder}/CMakeLists.txt")
else()
string(APPEND cml_files "/CMakeLists.txt")
endif()
file(STRINGS "${BOOST_SUPERPROJECT_SOURCE_DIR}/libs/${lib}/CMakeLists.txt" data)
foreach(cml_file IN LISTS cml_files)
if(NOT EXISTS "${cml_file}")
CONTINUE()
endif()
set(libs_to_exclude "")
file(STRINGS "${cml_file}" data)
foreach(line IN LISTS data)
if(line MATCHES "^ *# *Boost-(Include|Exclude):? *(.*)$")
set(type ${CMAKE_MATCH_1})
set(line ${CMAKE_MATCH_2})
else()
set(type "Include")
endif()
if(line MATCHES "^([^#]*Boost::[A-Za-z0-9_]+[^#]*)(#.*)?$")
string(REGEX MATCHALL "Boost::[A-Za-z0-9_]+" libs "${CMAKE_MATCH_1}")
if(line MATCHES "^[ ]*Boost::([A-Za-z0-9_]+)[ ]*$")
string(REGEX REPLACE "^numeric_" "numeric/" dep ${CMAKE_MATCH_1})
list(APPEND result ${dep})
foreach(dep IN LISTS libs)
string(REGEX REPLACE "^Boost::" "" dep ${dep})
if(dep STREQUAL "headers" OR dep STREQUAL "boost" OR dep MATCHES "linking")
continue()
endif()
if(dep MATCHES "unit_test_framework|prg_exec_monitor|test_exec_monitor")
set(dep "test")
elseif(dep STREQUAL "numpy")
set(dep "python")
elseif(dep MATCHES "serialization")
set(dep "serialization")
else()
string(REGEX REPLACE "^numeric_" "numeric/" dep ${dep})
foreach(prefix IN LISTS prefix_names)
if(dep MATCHES "^${prefix}_")
set(dep ${prefix})
break()
endif()
endforeach()
endif()
if(NOT dep STREQUAL lib)
if(type STREQUAL "Exclude")
list(APPEND libs_to_exclude ${dep})
else()
list(APPEND result ${dep})
endif()
endif()
endforeach()
endif()
endforeach()
endif()
endforeach()
if(libs_to_exclude)
list(REMOVE_ITEM result ${libs_to_exclude})
endif()
list(REMOVE_DUPLICATES result)
set(${var} ${result} PARENT_SCOPE)
endfunction()
@@ -269,38 +318,58 @@ endif()
# Scan for dependencies
set(__boost_include_libraries ${BOOST_INCLUDE_LIBRARIES})
function(__boost_gather_dependencies var input_list with_test)
set(result "")
if(__boost_include_libraries)
list(REMOVE_DUPLICATES __boost_include_libraries)
endif()
set(libs_to_scan ${input_list})
while(libs_to_scan)
set(__boost_libs_to_scan ${__boost_include_libraries})
boost_message(DEBUG "Scanning dependencies: ${libs_to_scan}")
while(__boost_libs_to_scan)
set(cur_dependencies "")
boost_message(DEBUG "Scanning dependencies: ${__boost_libs_to_scan}")
foreach(lib IN LISTS libs_to_scan)
set(__boost_dependencies "")
__boost_scan_dependencies(${lib} new_deps "")
list(APPEND cur_dependencies ${new_deps})
# Only consider test dependencies of the input libraries, not transitively as those tests aren't build
if(with_test AND lib IN_LIST input_list)
__boost_scan_dependencies(${lib} new_deps "test")
list(APPEND cur_dependencies ${new_deps})
__boost_scan_dependencies(${lib} new_deps "example")
list(APPEND cur_dependencies ${new_deps})
endif()
foreach(__boost_lib IN LISTS __boost_libs_to_scan)
endforeach()
__boost_scan_dependencies(${__boost_lib} __boost_deps)
list(APPEND __boost_dependencies ${__boost_deps})
list(REMOVE_DUPLICATES cur_dependencies)
endforeach()
if(cur_dependencies)
list(REMOVE_ITEM cur_dependencies ${libs_to_scan} ${result})
list(APPEND result ${cur_dependencies})
endif()
list(REMOVE_DUPLICATES __boost_dependencies)
set(libs_to_scan ${cur_dependencies})
set(__boost_libs_to_scan ${__boost_dependencies})
endwhile()
if(__boost_libs_to_scan)
list(REMOVE_ITEM __boost_libs_to_scan ${__boost_include_libraries})
list(REMOVE_ITEM result ${input_list})
set(${var} ${result} PARENT_SCOPE)
endfunction()
if(BOOST_INCLUDE_LIBRARIES)
list(REMOVE_DUPLICATES BOOST_INCLUDE_LIBRARIES)
__boost_gather_dependencies(__boost_dependencies "${BOOST_INCLUDE_LIBRARIES}" OFF)
if(BUILD_TESTING)
__boost_gather_dependencies(__boost_test_dependencies "${BOOST_INCLUDE_LIBRARIES}" ON)
if(__boost_dependencies)
list(REMOVE_ITEM __boost_test_dependencies ${__boost_dependencies})
endif()
endif()
list(APPEND __boost_include_libraries ${__boost_libs_to_scan})
endwhile()
else()
set(__boost_dependencies "")
set(__boost_test_dependencies "")
endif()
# Installing targets created in other directories requires CMake 3.13
if(CMAKE_VERSION VERSION_LESS 3.13)
@@ -342,7 +411,7 @@ foreach(__boost_lib_cml IN LISTS __boost_libraries)
__boost_auto_install(${__boost_lib})
__boost_add_header_only(${__boost_lib})
elseif(__boost_lib IN_LIST __boost_include_libraries OR __boost_lib STREQUAL "headers")
elseif(__boost_lib IN_LIST __boost_dependencies OR __boost_lib STREQUAL "headers")
# Disable tests for dependencies
@@ -364,9 +433,9 @@ foreach(__boost_lib_cml IN LISTS __boost_libraries)
set(BUILD_TESTING ${__boost_build_testing})
set(CMAKE_FOLDER ${__boost_cmake_folder})
elseif(BUILD_TESTING)
elseif(__boost_lib IN_LIST __boost_test_dependencies)
# Disable tests and installation for libraries neither included nor dependencies
# Disable tests and installation for libraries not included but used as test dependencies
set(__boost_build_testing ${BUILD_TESTING})
set(BUILD_TESTING OFF) # hide cache variable
@@ -380,7 +449,7 @@ foreach(__boost_lib_cml IN LISTS __boost_libraries)
set(CMAKE_FOLDER "Test Dependencies")
endif()
boost_message(DEBUG "Adding Boost library ${__boost_lib} with EXCLUDE_FROM_ALL")
boost_message(DEBUG "Adding Boost test dependency ${__boost_lib} with EXCLUDE_FROM_ALL")
add_subdirectory(libs/${__boost_lib} EXCLUDE_FROM_ALL)
set(BUILD_TESTING ${__boost_build_testing})