From fa10dc6c223531d79bbf7465a71fbd05f08c896a Mon Sep 17 00:00:00 2001 From: Vito Gamberini Date: Tue, 21 Oct 2025 15:55:19 -0400 Subject: [PATCH] Experimental/CXXModules: Implement EcoStd Module Metadata parser Adds a parser and serializer for the EcoStd Module Metadata format RFC: https://github.com/ecostd/rfcs/pull/3 This adapts the existing experimental support for import std; to use the new parser. The CMAKE_CXX_STDLIB_MODULES_JSON is now the canonical variable for controlling how CMake discovers the stdlib module metadata, and is used directly by compiler detection. Toolchains can still override the __CMAKE::CXX## targets if they wish, either in conjunction with CMAKE_CXX_STDLIB_MODULE_JSON or not. It is possible to disable automatic detection of CMAKE_CXX_STDLIB_MODULE_JSON by setting it to the empty string. When available, the CMAKE_CXX_STDLIB_MODULE_JSON will be used to create all requested C++ stdlibs which do not already have targets. --- Modules/CMakeCXXCompiler.cmake.in | 7 +- Modules/CMakeDetermineCompilerSupport.cmake | 28 +- .../Compiler/CMakeCommonCompilerMacros.cmake | 50 +- Modules/Compiler/Clang-CXX-CXXImportStd.cmake | 163 +------ Modules/Compiler/GNU-CXX-CXXImportStd.cmake | 124 +---- Modules/Compiler/MSVC-CXX-CXXImportStd.cmake | 169 +++---- Source/CMakeLists.txt | 2 + Source/cmCxxModuleMetadata.cxx | 460 ++++++++++++++++++ Source/cmCxxModuleMetadata.h | 84 ++++ Source/cmGeneratorTarget.cxx | 115 ++++- Source/cmGlobalGenerator.cxx | 12 +- Tests/RunCMake/CXXModules/Inspect.cmake | 1 + .../NoCXX23TargetRequired-stderr.txt | 7 +- .../NoCXX26TargetRequired-stderr.txt | 7 +- .../imp-std-exp-no-std-build-stderr.txt | 2 +- .../imp-std-exp-no-std-install-stderr.txt | 2 +- .../imp-std-no-std-prop-stderr.txt | 2 +- .../imp-std-not-in-exp-build-stderr.txt | 2 +- .../imp-std-not-in-exp-install-stderr.txt | 2 +- .../CXXModulesCompile/imp-std-stderr.txt | 2 +- .../imp-std-trans-exp-no-std-build-stderr.txt | 8 + ...mp-std-trans-exp-no-std-install-stderr.txt | 8 + .../imp-std-trans-not-in-exp-build-stderr.txt | 2 +- ...mp-std-trans-not-in-exp-install-stderr.txt | 2 +- .../property_init/CompileSources.cmake | 10 +- 25 files changed, 805 insertions(+), 466 deletions(-) create mode 100644 Source/cmCxxModuleMetadata.cxx create mode 100644 Source/cmCxxModuleMetadata.h create mode 100644 Tests/RunCMake/CXXModulesCompile/imp-std-trans-exp-no-std-build-stderr.txt create mode 100644 Tests/RunCMake/CXXModulesCompile/imp-std-trans-exp-no-std-install-stderr.txt diff --git a/Modules/CMakeCXXCompiler.cmake.in b/Modules/CMakeCXXCompiler.cmake.in index 8e2e3d643e..677402bd9e 100644 --- a/Modules/CMakeCXXCompiler.cmake.in +++ b/Modules/CMakeCXXCompiler.cmake.in @@ -96,5 +96,8 @@ set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "@CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES@") set(CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "@CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES@") set(CMAKE_CXX_COMPILER_CLANG_RESOURCE_DIR "@CMAKE_CXX_COMPILER_CLANG_RESOURCE_DIR@") -set(CMAKE_CXX_COMPILER_IMPORT_STD "") -@CMAKE_CXX_IMPORT_STD@ +set(CMAKE_CXX_COMPILER_IMPORT_STD "@CMAKE_CXX_COMPILER_IMPORT_STD@") +set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "@CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE@") +if(CMAKE_CXX_STDLIB_MODULES_JSON) + set(CMAKE_CXX_STDLIB_MODULES_JSON "@CMAKE_CXX_STDLIB_MODULES_JSON@") +endif() diff --git a/Modules/CMakeDetermineCompilerSupport.cmake b/Modules/CMakeDetermineCompilerSupport.cmake index c22bf07b45..512a1c4155 100644 --- a/Modules/CMakeDetermineCompilerSupport.cmake +++ b/Modules/CMakeDetermineCompilerSupport.cmake @@ -105,18 +105,21 @@ function(cmake_determine_compiler_support lang) ) endif() - # Create targets for use with `import std;` here. - set(CMAKE_CXX_IMPORT_STD "") - foreach (_cmake_import_std_version IN ITEMS 23 26) - if (CMAKE_CXX${_cmake_import_std_version}_COMPILE_FEATURES) - set(_cmake_cxx_import_std "") - cmake_create_cxx_import_std("${_cmake_import_std_version}" _cmake_cxx_import_std) - if (_cmake_cxx_import_std) - string(APPEND CMAKE_CXX_IMPORT_STD "### Imported target for C++${_cmake_import_std_version} standard library\n") - string(APPEND CMAKE_CXX_IMPORT_STD "${_cmake_cxx_import_std}\n\n") - endif () - endif () - endforeach () + # Find the module metadata for import std + set(CMAKE_CXX_COMPILER_IMPORT_STD "") + cmake_cxx_find_modules_json() + foreach(_cmake_import_std_version IN ITEMS 23 26) + if(CMAKE_CXX${_cmake_import_std_version}_COMPILE_FEATURES) + # Modules JSON covers all versions, otherwise rely on toolchain targets + if(CMAKE_CXX_STDLIB_MODULES_JSON OR (TARGET "__CMAKE:CXX${_cmake_import_std_version}")) + list(APPEND CMAKE_CXX_COMPILER_IMPORT_STD ${_cmake_import_std_version}) + endif() + endif() + endforeach() + + set(CMAKE_CXX_COMPILER_IMPORT_STD ${CMAKE_CXX_COMPILER_IMPORT_STD} PARENT_SCOPE) + set(CMAKE_CXX_STDLIB_MODULES_JSON ${CMAKE_CXX_STDLIB_MODULES_JSON} PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "${CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE}" PARENT_SCOPE) set(CMAKE_CXX_COMPILE_FEATURES ${CMAKE_CXX_COMPILE_FEATURES} PARENT_SCOPE) set(CMAKE_CXX98_COMPILE_FEATURES ${CMAKE_CXX98_COMPILE_FEATURES} PARENT_SCOPE) @@ -126,7 +129,6 @@ function(cmake_determine_compiler_support lang) set(CMAKE_CXX20_COMPILE_FEATURES ${CMAKE_CXX20_COMPILE_FEATURES} PARENT_SCOPE) set(CMAKE_CXX23_COMPILE_FEATURES ${CMAKE_CXX23_COMPILE_FEATURES} PARENT_SCOPE) set(CMAKE_CXX26_COMPILE_FEATURES ${CMAKE_CXX26_COMPILE_FEATURES} PARENT_SCOPE) - set(CMAKE_CXX_IMPORT_STD ${CMAKE_CXX_IMPORT_STD} PARENT_SCOPE) message(CHECK_PASS "done") diff --git a/Modules/Compiler/CMakeCommonCompilerMacros.cmake b/Modules/Compiler/CMakeCommonCompilerMacros.cmake index 067aa6f0ca..a97fe0fe54 100644 --- a/Modules/Compiler/CMakeCommonCompilerMacros.cmake +++ b/Modules/Compiler/CMakeCommonCompilerMacros.cmake @@ -202,43 +202,21 @@ macro(cmake_record_hip_compile_features) _has_compiler_features_hip(98) endmacro() -function(cmake_create_cxx_import_std std variable) - set(_cmake_supported_import_std_features - # Compilers support `import std` in C++20 as an extension. Skip - # for now. - # 20 - 23 - 26) - list(FIND _cmake_supported_import_std_features "${std}" _cmake_supported_import_std_idx) - if (_cmake_supported_import_std_idx EQUAL "-1") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Unsupported C++ standard: C++${std}\")\n" - PARENT_SCOPE) - return () - endif () - # If the target exists, skip. A toolchain file may have provided it. - if (TARGET "__CMAKE::CXX${std}") - return () - endif () +function(cmake_cxx_find_modules_json) # The generator must support imported C++ modules. if (NOT CMAKE_GENERATOR MATCHES "Ninja") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Unsupported generator: ${CMAKE_GENERATOR}\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Unsupported generator: ${CMAKE_GENERATOR}" PARENT_SCOPE) return () endif () + # Check if the compiler understands how to `import std;`. include("${CMAKE_ROOT}/Modules/Compiler/${CMAKE_CXX_COMPILER_ID}-CXX-CXXImportStd.cmake" OPTIONAL RESULT_VARIABLE _cmake_import_std_res) if (NOT _cmake_import_std_res) - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Toolchain does not support discovering `import std` support\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Toolchain does not support discovering module metadata" PARENT_SCOPE) return () endif () - if (NOT COMMAND _cmake_cxx_import_std) - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Toolchain does not provide `import std` discovery command\")\n" - PARENT_SCOPE) + if (NOT COMMAND _cmake_cxx_find_modules_json) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Toolchain does not provide module metadata discovery command" PARENT_SCOPE) return () endif () @@ -249,19 +227,11 @@ function(cmake_create_cxx_import_std std variable) "CxxImportStd" _cmake_supported_import_std_experimental) if (NOT _cmake_supported_import_std_experimental) - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Experimental `import std` support not enabled when detecting toolchain; it must be set before `CXX` is enabled (usually a `project()` call)\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Experimental `import std` support not enabled when detecting toolchain; it must be set before `CXX` is enabled (usually a `project()` call)" PARENT_SCOPE) return () endif () - _cmake_cxx_import_std("${std}" target_definition) - string(CONCAT guarded_target_definition - "if (NOT TARGET \"__CMAKE::CXX${std}\")\n" - "${target_definition}" - "endif ()\n" - "if (TARGET \"__CMAKE::CXX${std}\")\n" - " list(APPEND CMAKE_CXX_COMPILER_IMPORT_STD \"${std}\")\n" - "endif ()\n") - set("${variable}" "${guarded_target_definition}" PARENT_SCOPE) + _cmake_cxx_find_modules_json() + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "${CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE}" PARENT_SCOPE) + set(CMAKE_CXX_STDLIB_MODULES_JSON "${CMAKE_CXX_STDLIB_MODULES_JSON}" PARENT_SCOPE) endfunction() diff --git a/Modules/Compiler/Clang-CXX-CXXImportStd.cmake b/Modules/Compiler/Clang-CXX-CXXImportStd.cmake index 9061e006f7..4099a35e68 100644 --- a/Modules/Compiler/Clang-CXX-CXXImportStd.cmake +++ b/Modules/Compiler/Clang-CXX-CXXImportStd.cmake @@ -1,18 +1,14 @@ -function (_cmake_cxx_import_std std variable) +function (_cmake_cxx_find_modules_json) if (CMAKE_CXX_STANDARD_LIBRARY STREQUAL "libc++") set(_clang_modules_json_impl "libc++") elseif (CMAKE_CXX_STANDARD_LIBRARY STREQUAL "libstdc++") set(_clang_modules_json_impl "libstdc++") else () - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Only `libc++` and `libstdc++` are supported\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Only `libc++` and `libstdc++` are supported" PARENT_SCOPE) return () endif () - if (CMAKE_CXX_STDLIB_MODULES_JSON) - set(_clang_libcxx_modules_json_file "${CMAKE_CXX_STDLIB_MODULES_JSON}") - else () + if (NOT DEFINED CMAKE_CXX_STDLIB_MODULES_JSON) execute_process( COMMAND "${CMAKE_CXX_COMPILER}" @@ -24,19 +20,10 @@ function (_cmake_cxx_import_std std variable) OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) if (_clang_libcxx_modules_json_file_res) - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Could not find `${_clang_modules_json_impl}.modules.json` resource\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Could not find `${_clang_modules_json_impl}.modules.json` resource" PARENT_SCOPE) return () endif () - endif () - - # Without this file, we do not have modules installed. - if (NOT EXISTS "${_clang_libcxx_modules_json_file}") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`${_clang_modules_json_impl}.modules.json` resource does not exist\")\n" - PARENT_SCOPE) - return () + set(CMAKE_CXX_STDLIB_MODULES_JSON "${_clang_libcxx_modules_json_file}" PARENT_SCOPE) endif () if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "18.1.2" AND @@ -44,145 +31,7 @@ function (_cmake_cxx_import_std std variable) # The original PR had a key spelling mismatch internally. Do not support it # and instead require a release known to have the fix. # https://github.com/llvm/llvm-project/pull/83036 - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"LLVM 18.1.2 is required for `${_clang_modules_json_impl}.modules.json` format fix\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "LLVM 18.1.2 is required for `${_clang_modules_json_impl}.modules.json` format fix" PARENT_SCOPE) return () endif () - - file(READ "${_clang_libcxx_modules_json_file}" _clang_libcxx_modules_json) - string(JSON _clang_modules_json_version GET "${_clang_libcxx_modules_json}" "version") - string(JSON _clang_modules_json_revision GET "${_clang_libcxx_modules_json}" "revision") - # Require version 1. - if (NOT _clang_modules_json_version EQUAL "1") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`libc++.modules.json` version ${_clang_modules_json_version}.${_clang_modules_json_revision} is not recognized\")\n" - PARENT_SCOPE) - return () - endif () - - string(JSON _clang_modules_json_nmodules LENGTH "${_clang_libcxx_modules_json}" "modules") - # Don't declare the target without any modules. - if (NOT _clang_modules_json_nmodules) - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`libc++.modules.json` does not list any available modules\")\n" - PARENT_SCOPE) - return () - endif () - - # Declare the target. - set(_clang_libcxx_target "") - # Clang 18 does not provide the module initializer for the `std` modules. - # Create a static library to hold these. Hope that Clang 19 can provide this, - # but never run the code. - string(APPEND _clang_libcxx_target - "add_library(__cmake_cxx${std} STATIC)\n") - string(APPEND _clang_libcxx_target - "target_sources(__cmake_cxx${std} INTERFACE \"$<$,STATIC_LIBRARY>:$>\")\n") - string(APPEND _clang_libcxx_target - "set_property(TARGET __cmake_cxx${std} PROPERTY EXCLUDE_FROM_ALL 1)\n") - string(APPEND _clang_libcxx_target - "set_property(TARGET __cmake_cxx${std} PROPERTY CXX_SCAN_FOR_MODULES 1)\n") - string(APPEND _clang_libcxx_target - "set_property(TARGET __cmake_cxx${std} PROPERTY CXX_MODULE_STD 0)\n") - string(APPEND _clang_libcxx_target - "target_compile_features(__cmake_cxx${std} PUBLIC cxx_std_${std})\n") - - set(_clang_modules_is_stdlib 0) - set(_clang_modules_include_dirs_list "") - set(_clang_modules_module_paths "") - get_filename_component(_clang_modules_dir "${_clang_libcxx_modules_json_file}" DIRECTORY) - - # Add module sources. - math(EXPR _clang_modules_json_nmodules_range "${_clang_modules_json_nmodules} - 1") - foreach (_clang_modules_json_modules_idx RANGE 0 "${_clang_modules_json_nmodules_range}") - string(JSON _clang_modules_json_module GET "${_clang_libcxx_modules_json}" "modules" "${_clang_modules_json_modules_idx}") - - string(JSON _clang_modules_json_module_source GET "${_clang_modules_json_module}" "source-path") - string(JSON _clang_modules_json_module_is_stdlib GET "${_clang_modules_json_module}" "is-std-library") - string(JSON _clang_modules_json_module_local_arguments ERROR_VARIABLE _clang_modules_json_module_local_arguments_error GET "${_clang_modules_json_module}" "local-arguments") - string(JSON _clang_modules_json_module_nsystem_include_directories ERROR_VARIABLE _clang_modules_json_module_nsystem_include_directories_error LENGTH "${_clang_modules_json_module_local_arguments}" "system-include-directories") - - if (_clang_modules_json_module_local_arguments_error) - set(_clang_modules_json_module_local_arguments "") - endif () - if (_clang_modules_json_module_nsystem_include_directories_error) - set(_clang_modules_json_module_nsystem_include_directories 0) - endif () - if (NOT IS_ABSOLUTE "${_clang_modules_json_module_source}") - string(PREPEND _clang_modules_json_module_source "${_clang_modules_dir}/") - endif () - list(APPEND _clang_modules_module_paths - "${_clang_modules_json_module_source}") - - if (_clang_modules_json_module_is_stdlib) - set(_clang_modules_is_stdlib 1) - endif () - - if (_clang_modules_json_module_nsystem_include_directories) - math(EXPR _clang_modules_json_module_nsystem_include_directories_range "${_clang_modules_json_module_nsystem_include_directories} - 1") - foreach (_clang_modules_json_modules_system_include_directories_idx RANGE 0 "${_clang_modules_json_module_nsystem_include_directories_range}") - string(JSON _clang_modules_json_module_system_include_directory GET "${_clang_modules_json_module_local_arguments}" "system-include-directories" "${_clang_modules_json_modules_system_include_directories_idx}") - - if (NOT IS_ABSOLUTE "${_clang_modules_json_module_system_include_directory}") - string(PREPEND _clang_modules_json_module_system_include_directory "${_clang_modules_dir}/") - endif () - list(APPEND _clang_modules_include_dirs_list - "${_clang_modules_json_module_system_include_directory}") - endforeach () - endif () - endforeach () - - # Split the paths into basedirs and module paths. - set(_clang_modules_base_dirs_list "") - set(_clang_modules_files "") - foreach (_clang_modules_module_path IN LISTS _clang_modules_module_paths) - get_filename_component(_clang_module_dir "${_clang_modules_module_path}" DIRECTORY) - - list(APPEND _clang_modules_base_dirs_list - "${_clang_module_dir}") - string(APPEND _clang_modules_files - " \"${_clang_modules_module_path}\"") - endforeach () - list(REMOVE_DUPLICATES _clang_modules_base_dirs_list) - set(_clang_modules_base_dirs "") - foreach (_clang_modules_base_dir IN LISTS _clang_modules_base_dirs_list) - string(APPEND _clang_modules_base_dirs - " \"${_clang_modules_base_dir}\"") - endforeach () - - # If we have a standard library module, suppress warnings about reserved - # module names. - if (_clang_modules_is_stdlib) - string(APPEND _clang_libcxx_target - "target_compile_options(__cmake_cxx${std} PRIVATE -Wno-reserved-module-identifier)\n") - endif () - - # Set up include directories. - list(REMOVE_DUPLICATES _clang_modules_include_dirs_list) - set(_clang_modules_include_dirs "") - foreach (_clang_modules_include_dir IN LISTS _clang_modules_include_dirs_list) - string(APPEND _clang_modules_include_dirs - " \"${_clang_modules_include_dir}\"") - endforeach () - string(APPEND _clang_libcxx_target - "target_include_directories(__cmake_cxx${std} PRIVATE ${_clang_modules_include_dirs})\n") - - # Create the file set for the modules. - string(APPEND _clang_libcxx_target - "target_sources(__cmake_cxx${std} - PUBLIC - FILE_SET std TYPE CXX_MODULES - BASE_DIRS ${_clang_modules_base_dirs} - FILES ${_clang_modules_files})\n") - - # Wrap the `__cmake_cxx${std}` target in a check. - string(PREPEND _clang_libcxx_target - "if (NOT TARGET \"__cmake_cxx${std}\")\n") - string(APPEND _clang_libcxx_target - "endif ()\n") - string(APPEND _clang_libcxx_target - "add_library(__CMAKE::CXX${std} ALIAS __cmake_cxx${std})\n") - - set("${variable}" "${_clang_libcxx_target}" PARENT_SCOPE) endfunction () diff --git a/Modules/Compiler/GNU-CXX-CXXImportStd.cmake b/Modules/Compiler/GNU-CXX-CXXImportStd.cmake index 834e99912e..08d6273217 100644 --- a/Modules/Compiler/GNU-CXX-CXXImportStd.cmake +++ b/Modules/Compiler/GNU-CXX-CXXImportStd.cmake @@ -1,14 +1,10 @@ -function (_cmake_cxx_import_std std variable) +function (_cmake_cxx_find_modules_json) if (NOT CMAKE_CXX_STANDARD_LIBRARY STREQUAL "libstdc++") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Only `libstdc++` is supported\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Only `libstdc++` is supported" PARENT_SCOPE) return () endif () - if (CMAKE_CXX_STDLIB_MODULES_JSON) - set(_gnu_libstdcxx_modules_json_file "${CMAKE_CXX_STDLIB_MODULES_JSON}") - else () + if (NOT DEFINED CMAKE_CXX_STDLIB_MODULES_JSON) execute_process( COMMAND "${CMAKE_CXX_COMPILER}" @@ -20,119 +16,9 @@ function (_cmake_cxx_import_std std variable) OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) if (_gnu_libstdcxx_modules_json_file_res) - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Could not find `libstdc++.modules.json` resource\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Could not find `libstdc++.modules.json` resource" PARENT_SCOPE) return () endif () + set(CMAKE_CXX_STDLIB_MODULES_JSON "${_gnu_libstdcxx_modules_json_file}" PARENT_SCOPE) endif () - - # Without this file, we do not have modules installed. - if (NOT EXISTS "${_gnu_libstdcxx_modules_json_file}") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`libstdc++.modules.json` resource does not exist\")\n" - PARENT_SCOPE) - return () - endif () - - file(READ "${_gnu_libstdcxx_modules_json_file}" _gnu_libstdcxx_modules_json) - string(JSON _gnu_modules_json_version GET "${_gnu_libstdcxx_modules_json}" "version") - string(JSON _gnu_modules_json_revision GET "${_gnu_libstdcxx_modules_json}" "revision") - # Require version 1. - if (NOT _gnu_modules_json_version EQUAL "1") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`libstdc++.modules.json` version ${_gnu_modules_json_version}.${_gnu_modules_json_revision} is not recognized\")\n" - PARENT_SCOPE) - return () - endif () - - string(JSON _gnu_modules_json_nmodules LENGTH "${_gnu_libstdcxx_modules_json}" "modules") - # Don't declare the target without any modules. - if (NOT _gnu_modules_json_nmodules) - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`libstdc++.modules.json` does not list any available modules\")\n" - PARENT_SCOPE) - return () - endif () - - # Declare the target. - set(_gnu_libstdcxx_target "") - string(APPEND _gnu_libstdcxx_target - "add_library(__CMAKE::CXX${std} IMPORTED INTERFACE)\n") - string(APPEND _gnu_libstdcxx_target - "target_compile_features(__CMAKE::CXX${std} INTERFACE cxx_std_${std})\n") - - set(_gnu_modules_is_stdlib 0) - set(_gnu_modules_include_dirs_list "") - set(_gnu_modules_module_paths "") - get_filename_component(_gnu_modules_dir "${_gnu_libstdcxx_modules_json_file}" DIRECTORY) - - # Add module sources. - math(EXPR _gnu_modules_json_nmodules_range "${_gnu_modules_json_nmodules} - 1") - foreach (_gnu_modules_json_modules_idx RANGE 0 "${_gnu_modules_json_nmodules_range}") - string(JSON _gnu_modules_json_module GET "${_gnu_libstdcxx_modules_json}" "modules" "${_gnu_modules_json_modules_idx}") - - string(JSON _gnu_modules_json_module_source GET "${_gnu_modules_json_module}" "source-path") - string(JSON _gnu_modules_json_module_is_stdlib GET "${_gnu_modules_json_module}" "is-std-library") - string(JSON _gnu_modules_json_module_local_arguments ERROR_VARIABLE _gnu_modules_json_module_local_arguments_error GET "${_gnu_modules_json_module}" "local-arguments") - string(JSON _gnu_modules_json_module_nsystem_include_directories ERROR_VARIABLE _gnu_modules_json_module_nsystem_include_directories_error LENGTH "${_gnu_modules_json_module_local_arguments}" "system-include-directories") - - if (_gnu_modules_json_module_local_arguments_error STREQUAL "NOTFOUND") - set(_gnu_modules_json_module_local_arguments "") - endif () - if (_gnu_modules_json_module_nsystem_include_directories_error STREQUAL "NOTFOUND") - set(_gnu_modules_json_module_nsystem_include_directories 0) - endif () - - if (NOT IS_ABSOLUTE "${_gnu_modules_json_module_source}") - string(PREPEND _gnu_modules_json_module_source "${_gnu_modules_dir}/") - endif () - list(APPEND _gnu_modules_module_paths - "${_gnu_modules_json_module_source}") - - if (_gnu_modules_json_module_is_stdlib) - set(_gnu_modules_is_stdlib 1) - endif () - - if (_gnu_modules_json_module_nsystem_include_directories) - math(EXPR _gnu_modules_json_module_nsystem_include_directories_range "${_gnu_modules_json_module_nsystem_include_directories} - 1") - foreach (_gnu_modules_json_modules_system_include_directories_idx RANGE 0 "${_gnu_modules_json_module_nsystem_include_directories_range}") - string(JSON _gnu_modules_json_module_system_include_directory GET "${_gnu_modules_json_module_local_arguments}" "system-include-directories" "${_gnu_modules_json_modules_system_include_directories_idx}") - - if (NOT IS_ABSOLUTE "${_gnu_modules_json_module_system_include_directory}") - string(PREPEND _gnu_modules_json_module_system_include_directory "${_gnu_modules_dir}/") - endif () - list(APPEND _gnu_modules_include_dirs_list - "${_gnu_modules_json_module_system_include_directory}") - endforeach () - endif () - endforeach () - - # Split the paths into basedirs and module paths. - set(_gnu_modules_base_dirs_list "") - set(_gnu_modules_files "") - foreach (_gnu_modules_module_path IN LISTS _gnu_modules_module_paths) - get_filename_component(_gnu_module_dir "${_gnu_modules_module_path}" DIRECTORY) - - list(APPEND _gnu_modules_base_dirs_list - "${_gnu_module_dir}") - string(APPEND _gnu_modules_files - " \"${_gnu_modules_module_path}\"") - endforeach () - list(REMOVE_DUPLICATES _gnu_modules_base_dirs_list) - set(_gnu_modules_base_dirs "") - foreach (_gnu_modules_base_dir IN LISTS _gnu_modules_base_dirs_list) - string(APPEND _gnu_modules_base_dirs - " \"${_gnu_modules_base_dir}\"") - endforeach () - - # Create the file set for the modules. - string(APPEND _gnu_libstdcxx_target - "target_sources(__CMAKE::CXX${std} - INTERFACE - FILE_SET std TYPE CXX_MODULES - BASE_DIRS ${_gnu_modules_base_dirs} - FILES ${_gnu_modules_files})\n") - - set("${variable}" "${_gnu_libstdcxx_target}" PARENT_SCOPE) endfunction () diff --git a/Modules/Compiler/MSVC-CXX-CXXImportStd.cmake b/Modules/Compiler/MSVC-CXX-CXXImportStd.cmake index ff01263246..733b31beda 100644 --- a/Modules/Compiler/MSVC-CXX-CXXImportStd.cmake +++ b/Modules/Compiler/MSVC-CXX-CXXImportStd.cmake @@ -1,119 +1,104 @@ -function (_cmake_cxx_import_std std variable) - if (CMAKE_CXX_STDLIB_MODULES_JSON) - set(_msvc_modules_json_file "${CMAKE_CXX_STDLIB_MODULES_JSON}") - else () - find_file(_msvc_modules_json_file - NAME modules.json - HINTS - "$ENV{VCToolsInstallDir}/modules" - PATHS - "$ENV{INCLUDE}" - "${CMAKE_CXX_COMPILER}/../../.." - "${CMAKE_CXX_COMPILER}/../.." # msvc-wine layout - PATH_SUFFIXES - ../modules - NO_CACHE) - # Without this file, we do not have modules installed. - if (NOT EXISTS "${_msvc_modules_json_file}") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"Could not find `modules.json` resource\")\n" - PARENT_SCOPE) - return () - endif () +function (_cmake_cxx_find_modules_json) + if (DEFINED CMAKE_CXX_STDLIB_MODULES_JSON) + return () + endif () + + find_file(_msvc_modules_json_file + NAME modules.json + HINTS + "$ENV{VCToolsInstallDir}/modules" + PATHS + "$ENV{INCLUDE}" + "${CMAKE_CXX_COMPILER}/../../.." + "${CMAKE_CXX_COMPILER}/../.." # msvc-wine layout + PATH_SUFFIXES + ../modules + NO_CACHE) + + # Without this file, we do not have modules installed. + if (NOT EXISTS "${_msvc_modules_json_file}") + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Could not find `modules.json` resource" PARENT_SCOPE) + return () endif () file(READ "${_msvc_modules_json_file}" _msvc_modules_json) + string(JSON _msvc_json_modules ERROR_VARIABLE _msvc_json_err GET "${_msvc_modules_json}" "modules") + + # This is probably a conforming module metadata file, use it as is + if (_msvc_json_modules) + set(CMAKE_CXX_STDLIB_MODULES_JSON "${_msvc_modules_json_file}" PARENT_SCOPE) + return () + endif () + + # Otherwise it's a Microsoft STL-style modules.json, check if we recognize it string(JSON _msvc_json_version GET "${_msvc_modules_json}" "version") string(JSON _msvc_json_revision GET "${_msvc_modules_json}" "revision") # Require version 1. if (NOT _msvc_json_version EQUAL "1") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`modules.json` version ${_msvc_json_version}.${_msvc_json_revision} is not recognized\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "`modules.json` version ${_msvc_json_version}.${_msvc_json_revision} is not recognized" PARENT_SCOPE) return () endif () string(JSON _msvc_json_library GET "${_msvc_modules_json}" "library") # Bail if we don't understand the library. if (NOT _msvc_json_library STREQUAL "microsoft/STL") - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`modules.json` library `${_msvc_json_library}` is not recognized\")\n" - PARENT_SCOPE) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "`modules.json` library `${_msvc_json_library}` is not recognized" PARENT_SCOPE) return () endif () - string(JSON _msvc_json_nmodules LENGTH "${_msvc_modules_json}" "module-sources") - # Don't declare the target without any modules. - if (NOT _msvc_json_nmodules) - set("${variable}" - "set(CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE \"`modules.json` does not list any available modules\")\n" - PARENT_SCOPE) + string(JSON _msvc_json_sources_len LENGTH "${_msvc_modules_json}" "module-sources") + # Bail if there aren't any sources + if (NOT _msvc_json_sources_len) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "`modules.json` does not list any available sources" PARENT_SCOPE) return () endif () - # Declare the target. - set(_msvc_std_target "") - string(APPEND _msvc_std_target - "add_library(__cmake_cxx${std} STATIC)\n") - string(APPEND _msvc_std_target - "target_sources(__cmake_cxx${std} INTERFACE \"$<$,STATIC_LIBRARY>:$>\")\n") - string(APPEND _msvc_std_target - "set_property(TARGET __cmake_cxx${std} PROPERTY EXCLUDE_FROM_ALL 1)\n") - string(APPEND _msvc_std_target - "set_property(TARGET __cmake_cxx${std} PROPERTY CXX_SCAN_FOR_MODULES 1)\n") - string(APPEND _msvc_std_target - "set_property(TARGET __cmake_cxx${std} PROPERTY CXX_MODULE_STD 0)\n") - string(APPEND _msvc_std_target - "target_compile_features(__cmake_cxx${std} PUBLIC cxx_std_${std})\n") + set(_msvc_module_metadata [=[{ + "version": 1, + "revision": 1, + "modules": [] + }]=]) - set(_msvc_modules_module_paths "") - get_filename_component(_msvc_modules_dir "${_msvc_modules_json_file}" DIRECTORY) + cmake_path(GET _msvc_modules_json_file PARENT_PATH _msvc_base_dir) + math(EXPR _msvc_json_sources_len "${_msvc_json_sources_len}-1") + foreach (idx RANGE ${_msvc_json_sources_len}) + string(JSON _msvc_source GET "${_msvc_modules_json}" "module-sources" ${idx}) - # Add module sources. - math(EXPR _msvc_modules_json_nmodules_range "${_msvc_json_nmodules} - 1") - foreach (_msvc_modules_json_modules_idx RANGE 0 "${_msvc_modules_json_nmodules_range}") - string(JSON _msvc_modules_json_module_source GET "${_msvc_modules_json}" "module-sources" "${_msvc_modules_json_modules_idx}") - - if (NOT IS_ABSOLUTE "${_msvc_modules_json_module_source}") - string(PREPEND _msvc_modules_json_module_source "${_msvc_modules_dir}/") + set(_msvc_path ${_msvc_source}) + cmake_path(IS_RELATIVE _msvc_path _msvc_is_rel) + if (_msvc_is_rel) + cmake_path(ABSOLUTE_PATH _msvc_path BASE_DIRECTORY "${_msvc_base_dir}") endif () - list(APPEND _msvc_modules_module_paths - "${_msvc_modules_json_module_source}") - endforeach () - # Split the paths into basedirs and module paths. - set(_msvc_modules_base_dirs_list "") - set(_msvc_modules_files "") - foreach (_msvc_modules_module_path IN LISTS _msvc_modules_module_paths) - get_filename_component(_msvc_module_dir "${_msvc_modules_module_path}" DIRECTORY) + if (_msvc_source MATCHES "std.ixx") + string(JSON _msvc_module_metadata + SET "${_msvc_module_metadata}" "modules" ${idx} + "{ + \"logical-name\": \"std\", + \"source-path\": \"${_msvc_path}\", + \"is-std-library\": true + }" + ) + elseif (_msvc_source MATCHES "std.compat.ixx") + string(JSON _msvc_module_metadata + SET "${_msvc_module_metadata}" "modules" ${idx} + "{ + \"logical-name\": \"std.compat\", + \"source-path\": \"${_msvc_path}\", + \"is-std-library\": true + }" + ) + endif () + endforeach() - list(APPEND _msvc_modules_base_dirs_list - "${_msvc_module_dir}") - string(APPEND _msvc_modules_files - " \"${_msvc_modules_module_path}\"") - endforeach () - list(REMOVE_DUPLICATES _msvc_modules_base_dirs_list) - set(_msvc_modules_base_dirs "") - foreach (_msvc_modules_base_dir IN LISTS _msvc_modules_base_dirs_list) - string(APPEND _msvc_modules_base_dirs - " \"${_msvc_modules_base_dir}\"") - endforeach () + string(JSON _msvc_module_metadata_modules_len LENGTH ${_msvc_module_metadata} "modules") - # Create the file set for the modules. - string(APPEND _msvc_std_target - "target_sources(__cmake_cxx${std} - PUBLIC - FILE_SET std TYPE CXX_MODULES - BASE_DIRS ${_msvc_modules_base_dirs} - FILES ${_msvc_modules_files})\n") + # Bail if we didn't recognize any of the modules + if (NOT _msvc_module_metadata_modules_len) + set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "`modules.json` did not contain any recognized sources (std.ixx, std.compat.ixx)" PARENT_SCOPE) + return () + endif () - # Wrap the `__cmake_cxx${std}` target in a check. - string(PREPEND _msvc_std_target - "if (NOT TARGET \"__cmake_cxx${std}\")\n") - string(APPEND _msvc_std_target - "endif ()\n") - string(APPEND _msvc_std_target - "add_library(__CMAKE::CXX${std} ALIAS __cmake_cxx${std})\n") - - set("${variable}" "${_msvc_std_target}" PARENT_SCOPE) + file(WRITE "${CMAKE_PLATFORM_INFO_DIR}/msvcstl.modules.json" "${_msvc_module_metadata}") + set(CMAKE_CXX_STDLIB_MODULES_JSON "${CMAKE_PLATFORM_INFO_DIR}/msvcstl.modules.json" PARENT_SCOPE) endfunction () diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 4756bac131..1aacabcc5c 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -165,6 +165,8 @@ add_library( cmCustomCommandTypes.h cmCxxModuleMapper.cxx cmCxxModuleMapper.h + cmCxxModuleMetadata.cxx + cmCxxModuleMetadata.h cmCxxModuleUsageEffects.cxx cmCxxModuleUsageEffects.h cmDefinitions.cxx diff --git a/Source/cmCxxModuleMetadata.cxx b/Source/cmCxxModuleMetadata.cxx new file mode 100644 index 0000000000..b090305f3a --- /dev/null +++ b/Source/cmCxxModuleMetadata.cxx @@ -0,0 +1,460 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ + +#include "cmCxxModuleMetadata.h" + +#include +#include +#include +#include + +#include + +#include +#include + +#include "cmsys/FStream.hxx" + +#include "cmFileSet.h" +#include "cmJSONState.h" +#include "cmListFileCache.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmTarget.h" + +namespace { + +bool JsonIsStringArray(Json::Value const& v) +{ + return v.isArray() && + std::all_of(v.begin(), v.end(), + [](Json::Value const& it) { return it.isString(); }); +} + +bool ParsePreprocessorDefine(Json::Value& dval, + cmCxxModuleMetadata::PreprocessorDefineData& out, + cmJSONState* state) +{ + if (!dval.isObject()) { + state->AddErrorAtValue("each entry in 'definitions' must be an object", + &dval); + return false; + } + + if (!dval.isMember("name") || !dval["name"].isString() || + dval["name"].asString().empty()) { + state->AddErrorAtValue( + "preprocessor definition requires a non-empty 'name'", &dval["name"]); + return false; + } + out.Name = dval["name"].asString(); + + if (dval.isMember("value")) { + if (dval["value"].isString()) { + out.Value = dval["value"].asString(); + } else if (!dval["value"].isNull()) { + state->AddErrorAtValue( + "'value' in preprocessor definition must be string or null", + &dval["value"]); + return false; + } + } + + if (dval.isMember("undef")) { + if (!dval["undef"].isBool()) { + state->AddErrorAtValue( + "'undef' in preprocessor definition must be boolean", &dval["undef"]); + return false; + } + out.Undef = dval["undef"].asBool(); + } + + if (dval.isMember("vendor")) { + out.Vendor = std::move(dval["vendor"]); + } + + return true; +} + +bool ParseLocalArguments(Json::Value& lav, + cmCxxModuleMetadata::LocalArgumentsData& out, + cmJSONState* state) +{ + if (!lav.isObject()) { + state->AddErrorAtValue("'local-arguments' must be an object", &lav); + return false; + } + + if (lav.isMember("include-directories")) { + if (!JsonIsStringArray(lav["include-directories"])) { + state->AddErrorAtValue( + "'include-directories' must be an array of strings", + &lav["include-directories"]); + return false; + } + for (auto const& s : lav["include-directories"]) { + out.IncludeDirectories.push_back(s.asString()); + } + } + + if (lav.isMember("system-include-directories")) { + if (!JsonIsStringArray(lav["system-include-directories"])) { + state->AddErrorAtValue( + "'system-include-directories' must be an array of strings", + &lav["system-include-directories"]); + return false; + } + for (auto const& s : lav["system-include-directories"]) { + out.SystemIncludeDirectories.push_back(s.asString()); + } + } + + if (lav.isMember("definitions")) { + if (!lav["definitions"].isArray()) { + state->AddErrorAtValue("'definitions' must be an array", + &lav["definitions"]); + return false; + } + for (Json::Value& dval : lav["definitions"]) { + out.Definitions.emplace_back(); + if (!ParsePreprocessorDefine(dval, out.Definitions.back(), state)) { + return false; + } + } + } + + if (lav.isMember("vendor")) { + out.Vendor = std::move(lav["vendor"]); + } + + return true; +} + +bool ParseModule(Json::Value& mval, cmCxxModuleMetadata::ModuleData& mod, + cmJSONState* state) +{ + if (!mval.isObject()) { + state->AddErrorAtValue("each entry in 'modules' must be an object", &mval); + return false; + } + + if (!mval.isMember("logical-name") || !mval["logical-name"].isString() || + mval["logical-name"].asString().empty()) { + state->AddErrorAtValue( + "module entries require a non-empty 'logical-name' string", + &mval["logical-name"]); + return false; + } + mod.LogicalName = mval["logical-name"].asString(); + + if (!mval.isMember("source-path") || !mval["source-path"].isString() || + mval["source-path"].asString().empty()) { + state->AddErrorAtValue( + "module entries require a non-empty 'source-path' string", + &mval["source-path"]); + return false; + } + mod.SourcePath = mval["source-path"].asString(); + + if (mval.isMember("is-interface")) { + if (!mval["is-interface"].isBool()) { + state->AddErrorAtValue("'is-interface' must be boolean", + &mval["is-interface"]); + return false; + } + mod.IsInterface = mval["is-interface"].asBool(); + } else { + mod.IsInterface = true; + } + + if (mval.isMember("is-std-library")) { + if (!mval["is-std-library"].isBool()) { + state->AddErrorAtValue("'is-std-library' must be boolean", + &mval["is-std-library"]); + return false; + } + mod.IsStdLibrary = mval["is-std-library"].asBool(); + } else { + mod.IsStdLibrary = false; + } + + if (mval.isMember("local-arguments")) { + mod.LocalArguments.emplace(); + if (!ParseLocalArguments(mval["local-arguments"], *mod.LocalArguments, + state)) { + return false; + } + } + + if (mval.isMember("vendor")) { + mod.Vendor = std::move(mval["vendor"]); + } + + return true; +} + +bool ParseRoot(Json::Value& root, cmCxxModuleMetadata& meta, + cmJSONState* state) +{ + if (!root.isMember("version") || !root["version"].isInt()) { + state->AddErrorAtValue( + "Top-level member 'version' is required and must be an integer", &root); + return false; + } + meta.Version = root["version"].asInt(); + + if (root.isMember("revision")) { + if (!root["revision"].isInt()) { + state->AddErrorAtValue("'revision' must be an integer", + &root["revision"]); + return false; + } + meta.Revision = root["revision"].asInt(); + } + + if (root.isMember("modules")) { + if (!root["modules"].isArray()) { + state->AddErrorAtValue("'modules' must be an array", &root["modules"]); + return false; + } + for (Json::Value& mval : root["modules"]) { + meta.Modules.emplace_back(); + if (!ParseModule(mval, meta.Modules.back(), state)) { + return false; + } + } + } + + for (std::string& key : root.getMemberNames()) { + if (key == "version" || key == "revision" || key == "modules") { + continue; + } + meta.Extensions.emplace(std::move(key), std::move(root[key])); + } + + return true; +} + +} // namespace + +cmCxxModuleMetadata::ParseResult cmCxxModuleMetadata::LoadFromFile( + std::string const& path) +{ + ParseResult res; + + Json::Value root; + cmJSONState parseState(path, &root); + if (!parseState.errors.empty()) { + res.Error = parseState.GetErrorMessage(); + return res; + } + + cmCxxModuleMetadata meta; + if (!ParseRoot(root, meta, &parseState)) { + res.Error = parseState.GetErrorMessage(); + return res; + } + + meta.MetadataFilePath = path; + res.Meta = std::move(meta); + return res; +} + +namespace { + +Json::Value SerializePreprocessorDefine( + cmCxxModuleMetadata::PreprocessorDefineData const& d) +{ + Json::Value dv(Json::objectValue); + dv["name"] = d.Name; + if (d.Value) { + dv["value"] = *d.Value; + } else { + dv["value"] = Json::Value::null; + } + dv["undef"] = d.Undef; + if (d.Vendor) { + dv["vendor"] = *d.Vendor; + } + return dv; +} + +Json::Value SerializeLocalArguments( + cmCxxModuleMetadata::LocalArgumentsData const& la) +{ + Json::Value lav(Json::objectValue); + + if (!la.IncludeDirectories.empty()) { + Json::Value& inc = lav["include-directories"] = Json::arrayValue; + for (auto const& s : la.IncludeDirectories) { + inc.append(s); + } + } + + if (!la.SystemIncludeDirectories.empty()) { + Json::Value& sinc = lav["system-include-directories"] = Json::arrayValue; + for (auto const& s : la.SystemIncludeDirectories) { + sinc.append(s); + } + } + + if (!la.Definitions.empty()) { + Json::Value& defs = lav["definitions"] = Json::arrayValue; + for (auto const& d : la.Definitions) { + defs.append(SerializePreprocessorDefine(d)); + } + } + + if (la.Vendor) { + lav["vendor"] = *la.Vendor; + } + + return lav; +} + +Json::Value SerializeModule(cmCxxModuleMetadata::ModuleData const& m) +{ + Json::Value mv(Json::objectValue); + mv["logical-name"] = m.LogicalName; + mv["source-path"] = m.SourcePath; + mv["is-interface"] = m.IsInterface; + mv["is-std-library"] = m.IsStdLibrary; + + if (m.LocalArguments) { + mv["local-arguments"] = SerializeLocalArguments(*m.LocalArguments); + } + + if (m.Vendor) { + mv["vendor"] = *m.Vendor; + } + + return mv; +} + +} // namespace + +Json::Value cmCxxModuleMetadata::ToJsonValue(cmCxxModuleMetadata const& meta) +{ + Json::Value root(Json::objectValue); + + root["version"] = meta.Version; + root["revision"] = meta.Revision; + + Json::Value& modules = root["modules"] = Json::arrayValue; + for (auto const& m : meta.Modules) { + modules.append(SerializeModule(m)); + } + + for (auto const& kv : meta.Extensions) { + root[kv.first] = kv.second; + } + + return root; +} + +cmCxxModuleMetadata::SaveResult cmCxxModuleMetadata::SaveToFile( + std::string const& path, cmCxxModuleMetadata const& meta) +{ + SaveResult st; + + cmsys::ofstream ofs(path.c_str()); + if (!ofs.is_open()) { + st.Error = cmStrCat("Unable to open file for writing: "_s, path); + return st; + } + + Json::StreamWriterBuilder wbuilder; + wbuilder["indentation"] = " "; + ofs << Json::writeString(wbuilder, ToJsonValue(meta)); + + if (!ofs.good()) { + st.Error = cmStrCat("Write failed for file: "_s, path); + return st; + } + + st.Ok = true; + return st; +} + +void cmCxxModuleMetadata::PopulateTarget( + cmTarget& target, cmCxxModuleMetadata const& meta, + std::vector const& configs) +{ + std::vector allIncludeDirectories; + std::vector allCompileDefinitions; + std::set baseDirs; + + std::string metadataDir = + cmSystemTools::GetFilenamePath(meta.MetadataFilePath); + + auto fileSet = target.GetOrCreateFileSet("CXX_MODULES", "CXX_MODULES", + cmFileSetVisibility::Interface); + + for (auto const& module : meta.Modules) { + std::string sourcePath = module.SourcePath; + if (!cmSystemTools::FileIsFullPath(sourcePath)) { + sourcePath = cmStrCat(metadataDir, '/', sourcePath); + } + + // Module metadata files can reference files in different roots, + // just use the immediate parent directory as a base directory + baseDirs.insert(cmSystemTools::GetFilenamePath(sourcePath)); + + fileSet.first->AddFileEntry(sourcePath); + + if (module.LocalArguments) { + for (auto const& incDir : module.LocalArguments->IncludeDirectories) { + allIncludeDirectories.push_back(incDir); + } + for (auto const& sysIncDir : + module.LocalArguments->SystemIncludeDirectories) { + allIncludeDirectories.push_back(sysIncDir); + } + + for (auto const& def : module.LocalArguments->Definitions) { + if (!def.Undef) { + if (def.Value) { + allCompileDefinitions.push_back( + cmStrCat(def.Name, "="_s, *def.Value)); + } else { + allCompileDefinitions.push_back(def.Name); + } + } + } + } + } + + for (auto const& baseDir : baseDirs) { + fileSet.first->AddDirectoryEntry(baseDir); + } + + if (!allIncludeDirectories.empty()) { + target.SetProperty("IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES", + cmJoin(allIncludeDirectories, ";")); + } + + if (!allCompileDefinitions.empty()) { + target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS", + cmJoin(allCompileDefinitions, ";")); + } + + for (auto const& config : configs) { + std::vector moduleList; + for (auto const& module : meta.Modules) { + if (module.IsInterface) { + std::string sourcePath = module.SourcePath; + if (!cmSystemTools::FileIsFullPath(sourcePath)) { + sourcePath = cmStrCat(metadataDir, '/', sourcePath); + } + moduleList.push_back(cmStrCat(module.LogicalName, "="_s, sourcePath)); + } + } + + if (!moduleList.empty()) { + std::string upperConfig = cmSystemTools::UpperCase(config); + std::string propertyName = + cmStrCat("IMPORTED_CXX_MODULES_"_s, upperConfig); + target.SetProperty(propertyName, cmJoin(moduleList, ";")); + } + } +} diff --git a/Source/cmCxxModuleMetadata.h b/Source/cmCxxModuleMetadata.h new file mode 100644 index 0000000000..60769b8337 --- /dev/null +++ b/Source/cmCxxModuleMetadata.h @@ -0,0 +1,84 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +class cmTarget; + +class cmCxxModuleMetadata +{ +public: + struct PreprocessorDefineData + { + std::string Name; + cm::optional Value; + bool Undef = false; + cm::optional Vendor; + }; + + struct LocalArgumentsData + { + std::vector IncludeDirectories; + std::vector SystemIncludeDirectories; + std::vector Definitions; + cm::optional Vendor; + }; + + struct ModuleData + { + std::string LogicalName; + std::string SourcePath; + bool IsInterface = true; + bool IsStdLibrary = false; + cm::optional LocalArguments; + cm::optional Vendor; + }; + + int Version = 0; + int Revision = 0; + std::vector Modules; + std::string MetadataFilePath; + + std::unordered_map Extensions; + + struct ParseResult; + + struct SaveResult + { + bool Ok = false; + std::string Error; + explicit operator bool() const { return Ok; } + }; + + static ParseResult LoadFromFile(std::string const& path); + static Json::Value ToJsonValue(cmCxxModuleMetadata const& meta); + static SaveResult SaveToFile(std::string const& path, + cmCxxModuleMetadata const& meta); + static void PopulateTarget(cmTarget& target, cmCxxModuleMetadata const& meta, + std::vector const& configs); + static void PopulateTarget(cmTarget& target, cmCxxModuleMetadata const& meta, + cm::string_view config) + { + PopulateTarget(target, meta, + std::vector{ std::string(config) }); + } + static void PopulateTarget(cmTarget& target, cmCxxModuleMetadata const& meta) + { + PopulateTarget(target, meta, "NOCONFIG"); + } +}; + +struct cmCxxModuleMetadata::ParseResult +{ + cm::optional Meta; + std::string Error; + explicit operator bool() const { return static_cast(Meta); } +}; diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 3ff37b8df1..74eca71b74 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -22,6 +22,7 @@ #include "cmAlgorithms.h" #include "cmComputeLinkInformation.h" // IWYU pragma: keep #include "cmCryptoHash.h" +#include "cmCxxModuleMetadata.h" #include "cmCxxModuleUsageEffects.h" #include "cmExperimental.h" #include "cmFileSet.h" @@ -5126,6 +5127,70 @@ bool cmGeneratorTarget::IsNullImpliedByLinkLibraries( return cm::contains(this->LinkImplicitNullProperties, p); } +namespace { +bool CreateCxxStdlibTarget(cmMakefile* makefile, cmLocalGenerator* lg, + std::string const& targetName, + std::string const& cxxTargetName, + std::string const& stdLevel, + std::vector const& configs) +{ +#ifndef CMAKE_BOOTSTRAP + + static cm::optional metadata; + + // Load metadata only when we need to create a target + if (!metadata) { + auto errorMessage = + makefile->GetDefinition("CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE"); + if (!errorMessage.IsEmpty()) { + makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat(R"(The "CXX_MODULE_STD" property on target ")", targetName, + "\" requires toolchain support, but it was not provided. " + "Reason:\n ", + *errorMessage)); + return false; + } + + auto metadataPath = + makefile->GetDefinition("CMAKE_CXX_STDLIB_MODULES_JSON"); + if (metadataPath.IsEmpty()) { + makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat( + R"("The "CXX_MODULE_STD" property on target ")", targetName, + "\" requires CMAKE_CXX_STDLIB_MODULES_JSON be set, but it was not " + "provided by the toolchain.")); + return false; + } + + auto parseResult = cmCxxModuleMetadata::LoadFromFile(*metadataPath); + if (!parseResult) { + makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Failed to load C++ standard library modules metadata " + "from \"", + *metadataPath, "\": ", parseResult.Error)); + return false; + } + + metadata = std::move(*parseResult.Meta); + } + + auto* stdlibTgt = makefile->AddImportedTarget( + cxxTargetName, cmStateEnums::INTERFACE_LIBRARY, true); + cmCxxModuleMetadata::PopulateTarget(*stdlibTgt, *metadata, configs); + stdlibTgt->AppendProperty("IMPORTED_CXX_MODULES_COMPILE_FEATURES", + cmStrCat("cxx_std_", stdLevel)); + + lg->AddGeneratorTarget(cm::make_unique(stdlibTgt, lg)); + +#endif // CMAKE_BOOTSTRAP + + return true; +} +} // namespace + bool cmGeneratorTarget::ApplyCXXStdTargets() { cmStandardLevelResolver standardResolver(this->Makefile); @@ -5207,35 +5272,33 @@ bool cmGeneratorTarget::ApplyCXXStdTargets() auto const stdLevel = standardResolver.GetLevelString("CXX", *explicitLevel); - auto const targetName = cmStrCat("__CMAKE::CXX", stdLevel); - if (!this->Makefile->FindTargetToUse(targetName)) { - auto basicReason = this->Makefile->GetDefinition(cmStrCat( - "CMAKE_CXX", stdLevel, "_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE")); - std::string reason; - if (!basicReason.IsEmpty()) { - reason = cmStrCat(" Reason:\n ", basicReason); - } - this->Makefile->IssueMessage( - MessageType::FATAL_ERROR, - cmStrCat(R"(The "CXX_MODULE_STD" property on the target ")", - this->GetName(), "\" requires that the \"", targetName, - "\" target exist, but it was not provided by the toolchain.", - reason)); - break; - } + auto const cxxTargetName = cmStrCat("__CMAKE::CXX", stdLevel); - // Check the experimental feature here as well. A toolchain may have - // provided the target and skipped the check in the toolchain preparation - // logic. - if (!cmExperimental::HasSupportEnabled( - *this->Makefile, cmExperimental::Feature::CxxImportStd)) { - break; + // Create the __CMAKE::CXX## IMPORTED interface target if it doesn't + // already exist + if (!this->Makefile->FindTargetToUse(cxxTargetName) && + !CreateCxxStdlibTarget(this->Makefile, this->LocalGenerator, + this->GetName(), cxxTargetName, stdLevel, + configs)) { + return false; } this->Target->AppendProperty( "LINK_LIBRARIES", - cmStrCat("$:", targetName, - ">>")); + cmStrCat("$:", cxxTargetName, ">>")); + } + + // Check the experimental feature here. A toolchain may have + // skipped the check in the toolchain preparation logic. + if (!cmExperimental::HasSupportEnabled( + *this->Makefile, cmExperimental::Feature::CxxImportStd)) { + this->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + "Experimental `import std` support not enabled when detecting " + "toolchain; it must be set before `CXX` is enabled (usually a " + "`project()` call)."); + return false; } return true; @@ -5335,7 +5398,9 @@ bool cmGeneratorTarget::DiscoverSyntheticTargets(cmSyntheticTargetCache& cache, } // See `cmGlobalGenerator::ApplyCXXStdTargets` in // `cmGlobalGenerator::Compute` for non-synthetic target resolutions. - gtp->ApplyCXXStdTargets(); + if (!gtp->ApplyCXXStdTargets()) { + return false; + } gtp->DiscoverSyntheticTargets(cache, config); diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 4c40e9316d..7dc9705845 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -1787,7 +1787,17 @@ void cmGlobalGenerator::ComputeTargetOrder(cmGeneratorTarget const* gt, bool cmGlobalGenerator::ApplyCXXStdTargets() { for (auto const& gen : this->LocalGenerators) { - for (auto const& tgt : gen->GetGeneratorTargets()) { + + // tgt->ApplyCXXStd can create targets itself, so we need iterators which + // won't be invalidated by that target creation + auto const& genTgts = gen->GetGeneratorTargets(); + std::vector existingTgts; + existingTgts.reserve(genTgts.size()); + for (auto const& tgt : genTgts) { + existingTgts.push_back(tgt.get()); + } + + for (auto const& tgt : existingTgts) { if (!tgt->ApplyCXXStdTargets()) { return false; } diff --git a/Tests/RunCMake/CXXModules/Inspect.cmake b/Tests/RunCMake/CXXModules/Inspect.cmake index 2913c879ae..702e349e23 100644 --- a/Tests/RunCMake/CXXModules/Inspect.cmake +++ b/Tests/RunCMake/CXXModules/Inspect.cmake @@ -37,6 +37,7 @@ set(CMAKE_CXX_COMPILE_FEATURES \"${CMAKE_CXX_COMPILE_FEATURES}\") set(CMAKE_MAKE_PROGRAM \"${CMAKE_MAKE_PROGRAM}\") set(forced_cxx_standard \"${forced_cxx_standard}\") set(have_cxx23_import_std \"${have_cxx23_import_std}\") +set(have_cxx26_import_std \"${have_cxx26_import_std}\") set(CMAKE_CXX_COMPILER_VERSION \"${CMAKE_CXX_COMPILER_VERSION}\") set(CMAKE_CXX_OUTPUT_EXTENSION \"${CMAKE_CXX_OUTPUT_EXTENSION}\") set(CXXModules_default_build_type \"${CMAKE_BUILD_TYPE}\") diff --git a/Tests/RunCMake/CXXModules/NoCXX23TargetRequired-stderr.txt b/Tests/RunCMake/CXXModules/NoCXX23TargetRequired-stderr.txt index fd09dfa394..48a8b9480f 100644 --- a/Tests/RunCMake/CXXModules/NoCXX23TargetRequired-stderr.txt +++ b/Tests/RunCMake/CXXModules/NoCXX23TargetRequired-stderr.txt @@ -1,9 +1,8 @@ CMake Error in CMakeLists\.txt: - The "CXX_MODULE_STD" property on the target "nocxx23target" requires that - the "__CMAKE::CXX23" target exist, but it was not provided by the - toolchain\. Reason: + The "CXX_MODULE_STD" property on target "nocxx23target" requires toolchain + support, but it was not provided. Reason: - (Toolchain does not support discovering `import std` support|Experimental `import std` support not enabled when detecting toolchain; it must be set before `CXX` is enabled \(usually a `project\(\)` call\)|Unsupported generator: [^\n]*) + (Toolchain does not support discovering module metadata|Experimental `import std` support not enabled when detecting toolchain; it must be set before `CXX` is enabled \(usually a `project\(\)` call\)|Unsupported generator: [^\n]*) CMake Generate step failed\. Build files cannot be regenerated correctly\. diff --git a/Tests/RunCMake/CXXModules/NoCXX26TargetRequired-stderr.txt b/Tests/RunCMake/CXXModules/NoCXX26TargetRequired-stderr.txt index 836b39be39..b45b04e0d2 100644 --- a/Tests/RunCMake/CXXModules/NoCXX26TargetRequired-stderr.txt +++ b/Tests/RunCMake/CXXModules/NoCXX26TargetRequired-stderr.txt @@ -1,9 +1,8 @@ CMake Error in CMakeLists\.txt: - The "CXX_MODULE_STD" property on the target "nocxx26target" requires that - the "__CMAKE::CXX26" target exist, but it was not provided by the - toolchain\. Reason: + The "CXX_MODULE_STD" property on target "nocxx26target" requires toolchain + support, but it was not provided. Reason: - (Toolchain does not support discovering `import std` support|Experimental `import std` support not enabled when detecting toolchain; it must be set before `CXX` is enabled \(usually a `project\(\)` call\)|Unsupported generator: [^\n]*) + (Toolchain does not support discovering module metadata|Experimental `import std` support not enabled when detecting toolchain; it must be set before `CXX` is enabled \(usually a `project\(\)` call\)|Unsupported generator: [^\n]*) CMake Generate step failed\. Build files cannot be regenerated correctly\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-exp-no-std-build-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-exp-no-std-build-stderr.txt index 583556f56f..f7d285878e 100644 --- a/Tests/RunCMake/CXXModulesCompile/imp-std-exp-no-std-build-stderr.txt +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-exp-no-std-build-stderr.txt @@ -2,7 +2,7 @@ CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0 CMake's support for `import std;` in C\+\+23 and newer is experimental\. It is meant only for experimentation and feedback to CMake developers\. Call Stack \(most recent call first\): - .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_create_cxx_import_std\) + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) CMakeLists\.txt:[0-9]* \(project\) This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-exp-no-std-install-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-exp-no-std-install-stderr.txt index 583556f56f..f7d285878e 100644 --- a/Tests/RunCMake/CXXModulesCompile/imp-std-exp-no-std-install-stderr.txt +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-exp-no-std-install-stderr.txt @@ -2,7 +2,7 @@ CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0 CMake's support for `import std;` in C\+\+23 and newer is experimental\. It is meant only for experimentation and feedback to CMake developers\. Call Stack \(most recent call first\): - .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_create_cxx_import_std\) + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) CMakeLists\.txt:[0-9]* \(project\) This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-no-std-prop-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-no-std-prop-stderr.txt index 583556f56f..f7d285878e 100644 --- a/Tests/RunCMake/CXXModulesCompile/imp-std-no-std-prop-stderr.txt +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-no-std-prop-stderr.txt @@ -2,7 +2,7 @@ CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0 CMake's support for `import std;` in C\+\+23 and newer is experimental\. It is meant only for experimentation and feedback to CMake developers\. Call Stack \(most recent call first\): - .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_create_cxx_import_std\) + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) CMakeLists\.txt:[0-9]* \(project\) This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-not-in-exp-build-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-not-in-exp-build-stderr.txt index 583556f56f..f7d285878e 100644 --- a/Tests/RunCMake/CXXModulesCompile/imp-std-not-in-exp-build-stderr.txt +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-not-in-exp-build-stderr.txt @@ -2,7 +2,7 @@ CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0 CMake's support for `import std;` in C\+\+23 and newer is experimental\. It is meant only for experimentation and feedback to CMake developers\. Call Stack \(most recent call first\): - .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_create_cxx_import_std\) + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) CMakeLists\.txt:[0-9]* \(project\) This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-not-in-exp-install-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-not-in-exp-install-stderr.txt index 583556f56f..f7d285878e 100644 --- a/Tests/RunCMake/CXXModulesCompile/imp-std-not-in-exp-install-stderr.txt +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-not-in-exp-install-stderr.txt @@ -2,7 +2,7 @@ CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0 CMake's support for `import std;` in C\+\+23 and newer is experimental\. It is meant only for experimentation and feedback to CMake developers\. Call Stack \(most recent call first\): - .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_create_cxx_import_std\) + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) CMakeLists\.txt:[0-9]* \(project\) This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-stderr.txt index 583556f56f..f7d285878e 100644 --- a/Tests/RunCMake/CXXModulesCompile/imp-std-stderr.txt +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-stderr.txt @@ -2,7 +2,7 @@ CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0 CMake's support for `import std;` in C\+\+23 and newer is experimental\. It is meant only for experimentation and feedback to CMake developers\. Call Stack \(most recent call first\): - .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_create_cxx_import_std\) + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) CMakeLists\.txt:[0-9]* \(project\) This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-trans-exp-no-std-build-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-trans-exp-no-std-build-stderr.txt new file mode 100644 index 0000000000..f7d285878e --- /dev/null +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-trans-exp-no-std-build-stderr.txt @@ -0,0 +1,8 @@ +CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0-9]* \(cmake_language\): + CMake's support for `import std;` in C\+\+23 and newer is experimental\. It + is meant only for experimentation and feedback to CMake developers\. +Call Stack \(most recent call first\): + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) + .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) + CMakeLists\.txt:[0-9]* \(project\) +This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-trans-exp-no-std-install-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-trans-exp-no-std-install-stderr.txt new file mode 100644 index 0000000000..f7d285878e --- /dev/null +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-trans-exp-no-std-install-stderr.txt @@ -0,0 +1,8 @@ +CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0-9]* \(cmake_language\): + CMake's support for `import std;` in C\+\+23 and newer is experimental\. It + is meant only for experimentation and feedback to CMake developers\. +Call Stack \(most recent call first\): + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) + .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) + CMakeLists\.txt:[0-9]* \(project\) +This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-trans-not-in-exp-build-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-trans-not-in-exp-build-stderr.txt index 583556f56f..f7d285878e 100644 --- a/Tests/RunCMake/CXXModulesCompile/imp-std-trans-not-in-exp-build-stderr.txt +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-trans-not-in-exp-build-stderr.txt @@ -2,7 +2,7 @@ CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0 CMake's support for `import std;` in C\+\+23 and newer is experimental\. It is meant only for experimentation and feedback to CMake developers\. Call Stack \(most recent call first\): - .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_create_cxx_import_std\) + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) CMakeLists\.txt:[0-9]* \(project\) This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/CXXModulesCompile/imp-std-trans-not-in-exp-install-stderr.txt b/Tests/RunCMake/CXXModulesCompile/imp-std-trans-not-in-exp-install-stderr.txt index 583556f56f..f7d285878e 100644 --- a/Tests/RunCMake/CXXModulesCompile/imp-std-trans-not-in-exp-install-stderr.txt +++ b/Tests/RunCMake/CXXModulesCompile/imp-std-trans-not-in-exp-install-stderr.txt @@ -2,7 +2,7 @@ CMake Warning \(dev\) at .*/Modules/Compiler/CMakeCommonCompilerMacros\.cmake:[0 CMake's support for `import std;` in C\+\+23 and newer is experimental\. It is meant only for experimentation and feedback to CMake developers\. Call Stack \(most recent call first\): - .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_create_cxx_import_std\) + .*/Modules/CMakeDetermineCompilerSupport\.cmake:[0-9]* \(cmake_cxx_find_modules_json\) .*/Modules/CMakeTestCXXCompiler\.cmake:[0-9]* \(CMAKE_DETERMINE_COMPILER_SUPPORT\) CMakeLists\.txt:[0-9]* \(project\) This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/property_init/CompileSources.cmake b/Tests/RunCMake/property_init/CompileSources.cmake index ca3871d7ea..fe2b0fb287 100644 --- a/Tests/RunCMake/property_init/CompileSources.cmake +++ b/Tests/RunCMake/property_init/CompileSources.cmake @@ -102,7 +102,6 @@ set(properties "C_LINKER_LAUNCHER" "ccache" "" ### C++ "CXX_LINKER_LAUNCHER" "ccache" "" - "CXX_MODULE_STD" "ON" "" ### CUDA "CUDA_RESOLVE_DEVICE_SYMBOLS" "ON" "" "CUDA_RUNTIME_LIBRARY" "Static" "" @@ -229,6 +228,15 @@ macro (add_language_properties lang std) ) endmacro () +set(_cmake_supported_import_std_experimental "") +cmake_language(GET_EXPERIMENTAL_FEATURE_ENABLED "CxxImportStd" _cmake_supported_import_std_experimental) +if(_cmake_supported_import_std_experimental) + list(APPEND properties + # property expected alias + "CXX_MODULE_STD" "ON" "" + ) +endif() + # Mock up knowing the standard flag. This doesn't actually build, so nothing # should care at this point. set(CMAKE_Cc_std_11_STANDARD_COMPILE_OPTION "-std=c11")