diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41474a4..e0c69bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/developer.md b/developer.md index 104718e..190f71c 100644 --- a/developer.md +++ b/developer.md @@ -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 $) ``` or perhaps -``` +```cmake target_include_directories(boost_core INTERFACE $) @@ -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 -### Testing Support +# 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 $,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 `$,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 .. @@ -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 ctest --output-on-failure -j ``` Again, when using the Visual Studio generator, this would be -``` +```bash cmake --build . --target tests -j --config Debug ctest --output-on-failure -j -C Debug ``` resp. -``` +```bash cmake --build . --target tests -j --config Release ctest --output-on-failure -j -C Release ``` diff --git a/include/BoostRoot.cmake b/include/BoostRoot.cmake index 9e2f066..d693987 100644 --- a/include/BoostRoot.cmake +++ b/include/BoostRoot.cmake @@ -197,27 +197,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 "_" + set(prefix_names "asio" "dll" "fiber" "log" "regex" "stacktrace") set(result "") - if(EXISTS "${BOOST_SUPERPROJECT_SOURCE_DIR}/libs/${lib}/CMakeLists.txt") - - file(STRINGS "${BOOST_SUPERPROJECT_SOURCE_DIR}/libs/${lib}/CMakeLists.txt" data) + 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() - foreach(line IN LISTS data) + foreach(cml_file IN LISTS cml_files) + if(NOT EXISTS "${cml_file}") + CONTINUE() + endif() + set(libs_to_exclude "") - if(line MATCHES "^[ ]*Boost::([A-Za-z0-9_]+)[ ]*$") + file(STRINGS "${cml_file}" data) - string(REGEX REPLACE "^numeric_" "numeric/" dep ${CMAKE_MATCH_1}) - list(APPEND result ${dep}) + 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}") + + 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() @@ -264,38 +313,58 @@ endif() # Scan for dependencies -set(__boost_include_libraries ${BOOST_INCLUDE_LIBRARIES}) - -if(__boost_include_libraries) - list(REMOVE_DUPLICATES __boost_include_libraries) -endif() +function(__boost_gather_dependencies var input_list with_test) + set(result "") -set(__boost_libs_to_scan ${__boost_include_libraries}) + set(libs_to_scan ${input_list}) + while(libs_to_scan) -while(__boost_libs_to_scan) + boost_message(DEBUG "Scanning dependencies: ${libs_to_scan}") - boost_message(DEBUG "Scanning dependencies: ${__boost_libs_to_scan}") + set(cur_dependencies "") - set(__boost_dependencies "") + foreach(lib IN LISTS libs_to_scan) - foreach(__boost_lib IN LISTS __boost_libs_to_scan) + __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() - __boost_scan_dependencies(${__boost_lib} __boost_deps) - list(APPEND __boost_dependencies ${__boost_deps}) + endforeach() - endforeach() + list(REMOVE_DUPLICATES cur_dependencies) - list(REMOVE_DUPLICATES __boost_dependencies) + if(cur_dependencies) + list(REMOVE_ITEM cur_dependencies ${libs_to_scan} ${result}) + list(APPEND result ${cur_dependencies}) + endif() - set(__boost_libs_to_scan ${__boost_dependencies}) + set(libs_to_scan ${cur_dependencies}) - if(__boost_libs_to_scan) - list(REMOVE_ITEM __boost_libs_to_scan ${__boost_include_libraries}) - endif() + endwhile() - list(APPEND __boost_include_libraries ${__boost_libs_to_scan}) + list(REMOVE_ITEM result ${input_list}) + set(${var} ${result} PARENT_SCOPE) +endfunction() -endwhile() +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() +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) @@ -337,7 +406,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 @@ -359,9 +428,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 @@ -375,7 +444,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})