Merge topic 'cxx-scanning-properties'

a02d792c6e cxxmodules: add properties to control scanning
008c09d6db cmNinjaTargetGenerator: factor out determining the fileset of a source

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !7935
This commit is contained in:
Brad King
2022-11-21 15:00:00 +00:00
committed by Kitware Robot
17 changed files with 319 additions and 56 deletions
+2
View File
@@ -195,6 +195,7 @@ Properties on Targets
/prop_tgt/CXX_MODULE_SET
/prop_tgt/CXX_MODULE_SET_NAME
/prop_tgt/CXX_MODULE_SETS
/prop_tgt/CXX_SCAN_FOR_MODULES
/prop_tgt/CXX_STANDARD
/prop_tgt/CXX_STANDARD_REQUIRED
/prop_tgt/DEBUG_POSTFIX
@@ -533,6 +534,7 @@ Properties on Source Files
/prop_sf/COMPILE_DEFINITIONS
/prop_sf/COMPILE_FLAGS
/prop_sf/COMPILE_OPTIONS
/prop_sf/CXX_SCAN_FOR_MODULES
/prop_sf/EXTERNAL_OBJECT
/prop_sf/Fortran_FORMAT
/prop_sf/Fortran_PREPROCESS
+1
View File
@@ -417,6 +417,7 @@ Variables that Control the Build
/variable/CMAKE_CUDA_RESOLVE_DEVICE_SYMBOLS
/variable/CMAKE_CUDA_RUNTIME_LIBRARY
/variable/CMAKE_CUDA_SEPARABLE_COMPILATION
/variable/CMAKE_CXX_SCAN_FOR_MODULES
/variable/CMAKE_DEBUG_POSTFIX
/variable/CMAKE_DEFAULT_BUILD_TYPE
/variable/CMAKE_DEFAULT_CONFIGS
+19
View File
@@ -0,0 +1,19 @@
CXX_SCAN_FOR_MODULES
--------------------
.. versionadded:: 3.26
``CXX_SCAN_FOR_MODULES`` is a boolean specifying whether CMake will scan the
source for C++ module dependencies. See also the
:prop_tgt:`CXX_SCAN_FOR_MODULES` for target-wide settings.
When this property is set ``ON``, CMake will scan the source at build time and
add module dependency information to the compile line as necessary. When this
property is set ``OFF``, CMake will not scan the source at build time. When
this property is unset, the :prop_tgt:`CXX_SCAN_FOR_MODULES` property is
consulted.
Note that scanning is only performed if C++20 or higher is enabled for the
target and the source uses the ``CXX`` language. Scanning for modules in
sources belonging to file sets of type ``CXX_MODULES`` and
``CXX_MODULES_HEADER_UNITS`` is always performed.
+22
View File
@@ -0,0 +1,22 @@
CXX_SCAN_FOR_MODULES
--------------------
.. versionadded:: 3.26
``CXX_SCAN_FOR_MODULES`` is a boolean specifying whether CMake will scan C++
sources in the target for module dependencies. See also the
:prop_sf:`CXX_SCAN_FOR_MODULES` for per-source settings which, if set,
overrides the target-wide settings.
This property is initialized by the value of the
:variable:`CMAKE_CXX_SCAN_FOR_MODULES` variable if it is set when a target is
created.
When this property is set ``ON`` or unset, CMake will scan the target's
``CXX`` sources at build time and add module dependency information to the
compile line as necessary. When this property is set ``OFF``, CMake will not
scan the target's ``CXX`` sources at build time.
Note that scanning is only performed if C++20 or higher is enabled for the
target. Scanning for modules in the target's sources belonging to file sets
of type ``CXX_MODULES`` and ``CXX_MODULES_HEADER_UNITS`` is always performed.
@@ -0,0 +1,5 @@
cxx-scanning-properties
-----------------------
* The :prop_tgt:`CXX_SCAN_FOR_MODULES` target and source file properties may
be used to enable or disable scanning for C++ module dependencies.
@@ -0,0 +1,10 @@
CMAKE_CXX_SCAN_FOR_MODULES
--------------------------
.. versionadded:: 3.26
Whether to scan C++ source files for module dependencies.
This variable is used to initialize the :prop_tgt:`CXX_SCAN_FOR_MODULES`
property on all the targets. See that target property for additional
information.
+116 -55
View File
@@ -109,12 +109,13 @@ cmGlobalNinjaGenerator* cmNinjaTargetGenerator::GetGlobalGenerator() const
}
std::string cmNinjaTargetGenerator::LanguageCompilerRule(
const std::string& lang, const std::string& config) const
const std::string& lang, const std::string& config,
WithScanning withScanning) const
{
return cmStrCat(
lang, "_COMPILER__",
cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()),
'_', config);
withScanning == WithScanning::Yes ? "_scanned_" : "_unscanned_", config);
}
std::string cmNinjaTargetGenerator::LanguagePreprocessAndScanRule(
@@ -173,6 +174,90 @@ bool cmNinjaTargetGenerator::NeedDyndep(std::string const& lang,
return lang == "Fortran" || this->NeedCxxModuleSupport(lang, config);
}
void cmNinjaTargetGenerator::BuildFileSetInfoCache(std::string const& config)
{
auto& per_config = this->Configs[config];
if (per_config.BuiltFileSetCache) {
return;
}
auto const* tgt = this->GeneratorTarget->Target;
for (auto const& name : tgt->GetAllFileSetNames()) {
auto const* file_set = tgt->GetFileSet(name);
if (!file_set) {
this->GetMakefile()->IssueMessage(
MessageType::INTERNAL_ERROR,
cmStrCat("Target \"", tgt->GetName(),
"\" is tracked to have file set \"", name,
"\", but it was not found."));
continue;
}
auto fileEntries = file_set->CompileFileEntries();
auto directoryEntries = file_set->CompileDirectoryEntries();
auto directories = file_set->EvaluateDirectoryEntries(
directoryEntries, this->LocalGenerator, config, this->GeneratorTarget);
std::map<std::string, std::vector<std::string>> files;
for (auto const& entry : fileEntries) {
file_set->EvaluateFileEntry(directories, files, entry,
this->LocalGenerator, config,
this->GeneratorTarget);
}
for (auto const& it : files) {
for (auto const& filename : it.second) {
per_config.FileSetCache[filename] = file_set;
}
}
}
per_config.BuiltFileSetCache = true;
}
cmFileSet const* cmNinjaTargetGenerator::GetFileSetForSource(
std::string const& config, cmSourceFile const* sf)
{
this->BuildFileSetInfoCache(config);
auto const& path = sf->GetFullPath();
auto const& per_config = this->Configs[config];
auto const fsit = per_config.FileSetCache.find(path);
if (fsit == per_config.FileSetCache.end()) {
return nullptr;
}
return fsit->second;
}
bool cmNinjaTargetGenerator::NeedDyndepForSource(std::string const& lang,
std::string const& config,
cmSourceFile const* sf)
{
bool const needDyndep = this->NeedDyndep(lang, config);
if (!needDyndep) {
return false;
}
auto const* fs = this->GetFileSetForSource(config, sf);
if (fs &&
(fs->GetType() == "CXX_MODULES"_s ||
fs->GetType() == "CXX_MODULE_HEADER_UNITS"_s)) {
return true;
}
auto const sfProp = sf->GetProperty("CXX_SCAN_FOR_MODULES");
if (sfProp.IsSet()) {
return sfProp.IsOn();
}
auto const tgtProp =
this->GeneratorTarget->GetProperty("CXX_SCAN_FOR_MODULES");
if (tgtProp.IsSet()) {
return tgtProp.IsOn();
}
return true;
}
std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget(
const std::string& config)
{
@@ -256,54 +341,17 @@ std::string cmNinjaTargetGenerator::ComputeFlagsForObject(
flags, genexInterpreter.Evaluate(pchOptions, COMPILE_OPTIONS));
}
auto const& path = source->GetFullPath();
auto const* tgt = this->GeneratorTarget->Target;
std::string file_set_type;
for (auto const& name : tgt->GetAllFileSetNames()) {
auto const* file_set = tgt->GetFileSet(name);
if (!file_set) {
auto const* fs = this->GetFileSetForSource(config, source);
if (fs &&
(fs->GetType() == "CXX_MODULES"_s ||
fs->GetType() == "CXX_MODULE_HEADER_UNITS"_s)) {
if (source->GetLanguage() != "CXX"_s) {
this->GetMakefile()->IssueMessage(
MessageType::INTERNAL_ERROR,
cmStrCat("Target \"", tgt->GetName(),
"\" is tracked to have file set \"", name,
"\", but it was not found."));
continue;
}
auto fileEntries = file_set->CompileFileEntries();
auto directoryEntries = file_set->CompileDirectoryEntries();
auto directories = file_set->EvaluateDirectoryEntries(
directoryEntries, this->LocalGenerator, config, this->GeneratorTarget);
std::map<std::string, std::vector<std::string>> files;
for (auto const& entry : fileEntries) {
file_set->EvaluateFileEntry(directories, files, entry,
this->LocalGenerator, config,
this->GeneratorTarget);
}
for (auto const& it : files) {
for (auto const& filename : it.second) {
if (filename == path) {
file_set_type = file_set->GetType();
break;
}
}
}
if (file_set_type == "CXX_MODULES"_s ||
file_set_type == "CXX_MODULE_HEADER_UNITS"_s) {
if (source->GetLanguage() != "CXX"_s) {
this->GetMakefile()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat(
"Target \"", tgt->GetName(), "\" contains the source\n ", path,
"\nin a file set of type \"", file_set_type,
R"(" but the source is not classified as a "CXX" source.)"));
continue;
}
MessageType::FATAL_ERROR,
cmStrCat("Target \"", this->GeneratorTarget->Target->GetName(),
"\" contains the source\n ", source->GetFullPath(),
"\nin a file set of type \"", fs->GetType(),
R"(" but the source is not classified as a "CXX" source.)"));
}
}
@@ -649,6 +697,19 @@ cmNinjaRule GetScanRule(
void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
const std::string& config)
{
// For some cases we scan to dynamically discover dependencies.
bool const needDyndep = this->NeedDyndep(lang, config);
if (needDyndep) {
this->WriteCompileRule(lang, config, WithScanning::Yes);
}
this->WriteCompileRule(lang, config, WithScanning::No);
}
void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
const std::string& config,
WithScanning withScanning)
{
cmRulePlaceholderExpander::RuleVariables vars;
vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str();
@@ -669,7 +730,6 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
cmMakefile* mf = this->GetMakefile();
// For some cases we scan to dynamically discover dependencies.
bool const needDyndep = this->NeedDyndep(lang, config);
bool const compilationPreprocesses = !this->NeedExplicitPreprocessing(lang);
std::string flags = "$FLAGS";
@@ -707,7 +767,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
this->GetLocalGenerator()->ConvertToOutputFormat(
cmSystemTools::GetCMakeCommand(), cmLocalGenerator::SHELL);
if (needDyndep) {
if (withScanning == WithScanning::Yes) {
const auto& scanDepType = this->GetMakefile()->GetSafeDefinition(
cmStrCat("CMAKE_EXPERIMENTAL_", lang, "_SCANDEP_DEPFILE_FORMAT"));
@@ -813,7 +873,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
this->GetGlobalGenerator()->AddRule(rule);
}
cmNinjaRule rule(this->LanguageCompilerRule(lang, config));
cmNinjaRule rule(this->LanguageCompilerRule(lang, config, withScanning));
// If using a response file, move defines, includes, and flags into it.
if (!responseFlag.empty()) {
rule.RspFile = "$RSP_FILE";
@@ -867,7 +927,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
}
}
if (needDyndep && !modmapFormat.empty()) {
if (withScanning == WithScanning::Yes && !modmapFormat.empty()) {
std::string modmapFlags = mf->GetRequiredDefinition(
cmStrCat("CMAKE_EXPERIMENTAL_", lang, "_MODULE_MAP_FLAG"));
cmSystemTools::ReplaceString(modmapFlags, "<MODULE_MAP_FILE>",
@@ -1327,8 +1387,10 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
!(language == "RC" || (language == "CUDA" && !flag));
int const commandLineLengthLimit =
((lang_supports_response && this->ForceResponseFile())) ? -1 : 0;
bool const needDyndep = this->NeedDyndepForSource(language, config, source);
cmNinjaBuild objBuild(this->LanguageCompilerRule(language, config));
cmNinjaBuild objBuild(this->LanguageCompilerRule(
language, config, needDyndep ? WithScanning::Yes : WithScanning::No));
cmNinjaVars& vars = objBuild.Variables;
vars["FLAGS"] = this->ComputeFlagsForObject(source, language, config);
vars["DEFINES"] = this->ComputeDefines(source, language, config);
@@ -1437,7 +1499,6 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
}
// For some cases we scan to dynamically discover dependencies.
bool const needDyndep = this->NeedDyndep(language, config);
bool const compilationPreprocesses =
!this->NeedExplicitPreprocessing(language);
+18 -1
View File
@@ -19,6 +19,7 @@
#include "cmOSXBundleGenerator.h"
class cmCustomCommand;
class cmFileSet;
class cmGeneratedFileStream;
class cmGeneratorTarget;
class cmLocalNinjaGenerator;
@@ -63,8 +64,18 @@ protected:
cmMakefile* GetMakefile() const { return this->Makefile; }
void BuildFileSetInfoCache(std::string const& config);
cmFileSet const* GetFileSetForSource(std::string const& config,
cmSourceFile const* sf);
enum class WithScanning
{
No,
Yes,
};
std::string LanguageCompilerRule(const std::string& lang,
const std::string& config) const;
const std::string& config,
WithScanning withScanning) const;
std::string LanguagePreprocessAndScanRule(std::string const& lang,
const std::string& config) const;
std::string LanguageScanRule(std::string const& lang,
@@ -72,6 +83,8 @@ protected:
std::string LanguageDyndepRule(std::string const& lang,
const std::string& config) const;
bool NeedDyndep(std::string const& lang, std::string const& config) const;
bool NeedDyndepForSource(std::string const& lang, std::string const& config,
cmSourceFile const* sf);
bool NeedExplicitPreprocessing(std::string const& lang) const;
bool CompileWithDefines(std::string const& lang) const;
bool NeedCxxModuleSupport(std::string const& lang,
@@ -150,6 +163,8 @@ protected:
const std::string& config);
void WriteCompileRule(const std::string& language,
const std::string& config);
void WriteCompileRule(const std::string& language, const std::string& config,
WithScanning withScanning);
void WriteObjectBuildStatements(const std::string& config,
const std::string& fileConfig,
bool firstForConfig);
@@ -223,6 +238,8 @@ private:
std::vector<cmCustomCommand const*> CustomCommands;
cmNinjaDeps ExtraFiles;
std::unique_ptr<MacOSXContentGeneratorType> MacOSXContentGenerator;
bool BuiltFileSetCache = false;
std::map<std::string, cmFileSet const*> FileSetCache;
};
std::map<std::string, ByConfig> Configs;
+1
View File
@@ -526,6 +526,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
initProp("ANDROID_ANT_ADDITIONAL_OPTIONS");
initProp("BUILD_RPATH");
initProp("BUILD_RPATH_USE_ORIGIN");
initProp("CXX_SCAN_FOR_MODULES");
initProp("INSTALL_NAME_DIR");
initProp("INSTALL_REMOVE_ENVIRONMENT_RPATH");
initPropValue("INSTALL_RPATH", "");
@@ -138,6 +138,7 @@ if ("named" IN_LIST CMake_TEST_MODULE_COMPILATION)
set(RunCMake_CXXModules_NO_TEST 1)
run_cxx_module_test(circular)
unset(RunCMake_CXXModules_NO_TEST)
run_cxx_module_test(scan_properties)
endif ()
# Tests which use named modules in shared libraries.
@@ -0,0 +1,9 @@
CMake Warning \(dev\) at CMakeLists.txt:20 \(target_sources\):
CMake's C\+\+ module support is experimental. It is meant only for
experimentation and feedback to CMake developers.
This warning is for project developers. Use -Wno-dev to suppress it.
CMake Warning \(dev\):
C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
experimental. It is meant only for compiler developers to try.
This warning is for project developers. Use -Wno-dev to suppress it.
@@ -0,0 +1,54 @@
cmake_minimum_required(VERSION 3.24)
project(scan_properties CXX)
include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
# To detect that not-to-be scanned sources are not scanned, add a `-D` to the
# scan flags so that the files can detect whether scanning happened and error
# if not.
string(APPEND CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG
" -DCMAKE_SCANNED_THIS_SOURCE")
string(APPEND CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE
" -DCMAKE_SCANNED_THIS_SOURCE")
set_property(SOURCE always_scan.cxx
PROPERTY CXX_SCAN_FOR_MODULES 1)
set_property(SOURCE never_scan.cxx
PROPERTY CXX_SCAN_FOR_MODULES 0)
add_executable(scans_everything)
target_sources(scans_everything
PRIVATE
main.cxx
join.cxx
always_scan.cxx
never_scan.cxx
PRIVATE
FILE_SET CXX_MODULES
BASE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}"
FILES
module.cxx)
target_compile_features(scans_everything PRIVATE cxx_std_20)
target_compile_definitions(scans_everything PRIVATE SCAN_AT_TARGET_LEVEL=1)
set(CMAKE_CXX_SCAN_FOR_MODULES 0)
add_executable(no_scan_everything)
target_sources(no_scan_everything
PRIVATE
main.cxx
join.cxx
always_scan.cxx
never_scan.cxx
PRIVATE
FILE_SET CXX_MODULES
BASE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}"
FILES
module.cxx)
target_compile_features(no_scan_everything PRIVATE cxx_std_20)
target_compile_definitions(no_scan_everything PRIVATE SCAN_AT_TARGET_LEVEL=0)
add_test(NAME scanned COMMAND scans_everything)
add_test(NAME unscanned COMMAND no_scan_everything)
@@ -0,0 +1,10 @@
#ifndef CMAKE_SCANNED_THIS_SOURCE
# error "This file should have been scanned"
#endif
import M;
int scanned_file()
{
return from_module();
}
@@ -0,0 +1,17 @@
#if SCAN_AT_TARGET_LEVEL
# ifndef CMAKE_SCANNED_THIS_SOURCE
# error "This file should have been scanned"
# endif
#else
# ifdef CMAKE_SCANNED_THIS_SOURCE
# error "This file should not have been scanned"
# endif
#endif
int scanned_file();
int never_scan();
int join()
{
return scanned_file() + never_scan();
}
@@ -0,0 +1,16 @@
#if SCAN_AT_TARGET_LEVEL
# ifndef CMAKE_SCANNED_THIS_SOURCE
# error "This file should have been scanned"
# endif
#else
# ifdef CMAKE_SCANNED_THIS_SOURCE
# error "This file should not have been scanned"
# endif
#endif
int join();
int main(int argc, char** argv)
{
return join();
}
@@ -0,0 +1,10 @@
#ifndef CMAKE_SCANNED_THIS_SOURCE
# error "This file should have been scanned"
#endif
export module M;
export int from_module()
{
return 0;
}
@@ -0,0 +1,8 @@
#ifdef CMAKE_SCANNED_THIS_SOURCE
# error "This file should not have been scanned"
#endif
int never_scan()
{
return 0;
}