cxxmodules: Generate per-importer BMIs for native targets

C++ module importers may have specific usage requirements that differ from
other importers of the same module, potentially requiring separate Built Module
Interfaces (BMIs) for compatibility. CMake creates synthetic targets to
generate and link usage-specific BMIs. This change extends CMake's per-importer
BMI generation to native targets in addition to imported targets.
This commit is contained in:
Daniel Tierney
2026-02-09 14:16:17 -05:00
committed by Brad King
parent 62bcbcb19c
commit 5898c8d2e0
19 changed files with 418 additions and 153 deletions
+82 -27
View File
@@ -180,35 +180,90 @@ cmCommonTargetGenerator::GetLinkedTargetDirectories(
if (cmComputeLinkInformation* cli =
this->GeneratorTarget->GetLinkInformation(config)) {
auto addLinkedTarget =
[this, &lang, &config, &dirs, &direct_emitted, &forward_emitted,
gg](cmGeneratorTarget const* linkee, Forwarding forward) {
if (linkee &&
!linkee->IsImported()
// Skip targets that build after this one in a static lib cycle.
&& gg->TargetOrderIndexLess(linkee, this->GeneratorTarget)
// We can ignore the INTERFACE_LIBRARY items because
// Target->GetLinkInformation already processed their
// link interface and they don't have any output themselves.
&& (linkee->GetType() != cmStateEnums::INTERFACE_LIBRARY
// Synthesized targets may have relevant rules.
|| linkee->IsSynthetic()) &&
((lang == "CXX"_s && linkee->HaveCxx20ModuleSources()) ||
(lang == "Fortran"_s && linkee->HaveFortranSources(config)))) {
cmLocalGenerator* lg = linkee->GetLocalGenerator();
std::string di = linkee->GetSupportDirectory();
if (lg->GetGlobalGenerator()->IsMultiConfig()) {
di = cmStrCat(di, '/', config);
}
if (forward == Forwarding::Yes &&
forward_emitted.insert(linkee).second) {
dirs.Forward.push_back(di);
}
if (direct_emitted.insert(linkee).second) {
dirs.Direct.emplace_back(di);
auto findSyntheticTarget =
[this,
&config](cmGeneratorTarget const* linkee) -> cmGeneratorTarget const* {
if (!linkee) {
return nullptr;
}
// Check the map of direct synthetic dependencies for a substitute
auto const& synthDeps = this->GeneratorTarget->GetSyntheticDeps(config);
auto it = synthDeps.find(linkee);
if (it != synthDeps.end() && !it->second.empty()) {
return it->second.front();
}
// Check linked targets to finding synthetic targets for transitive deps
std::vector<cmGeneratorTarget const*> pending;
std::set<cmGeneratorTarget const*> visited;
for (auto const& dep : synthDeps) {
for (auto const* synth : dep.second) {
if (synth && visited.insert(synth).second) {
pending.push_back(synth);
}
}
};
}
while (!pending.empty()) {
auto const* current = pending.back();
pending.pop_back();
auto const& transitiveSynthDeps = current->GetSyntheticDeps(config);
auto itLinkeeSynth = transitiveSynthDeps.find(linkee);
if (itLinkeeSynth != transitiveSynthDeps.end() &&
!itLinkeeSynth->second.empty()) {
return itLinkeeSynth->second.front();
}
for (auto const& entry : transitiveSynthDeps) {
for (auto const* synth : entry.second) {
if (synth && visited.insert(synth).second) {
pending.push_back(synth);
}
}
}
}
return nullptr;
};
auto addLinkedTarget = [this, &lang, &config, &dirs, &direct_emitted,
&forward_emitted, &findSyntheticTarget,
gg](cmGeneratorTarget const* linkee,
Forwarding forward) {
// Check if the linkee has a synthetic target to use for importing
cmGeneratorTarget const* mappedLinkee = linkee;
if (auto const* synth = findSyntheticTarget(linkee)) {
mappedLinkee = synth;
}
if (mappedLinkee &&
!mappedLinkee->IsImported()
// Skip targets that build after this one in a static lib cycle.
&& gg->TargetOrderIndexLess(mappedLinkee, this->GeneratorTarget)
// We can ignore the INTERFACE_LIBRARY items because
// Target->GetLinkInformation already processed their
// link interface and they don't have any output themselves.
&& (mappedLinkee->GetType() != cmStateEnums::INTERFACE_LIBRARY
// Synthesized targets may have relevant rules.
|| mappedLinkee->IsSynthetic()) &&
((lang == "CXX"_s && mappedLinkee->HaveCxx20ModuleSources()) ||
(lang == "Fortran"_s &&
mappedLinkee->HaveFortranSources(config)))) {
cmLocalGenerator* lg = mappedLinkee->GetLocalGenerator();
std::string di = mappedLinkee->GetSupportDirectory();
if (lg->GetGlobalGenerator()->IsMultiConfig()) {
di = cmStrCat(di, '/', config);
}
if (forward == Forwarding::Yes &&
forward_emitted.insert(mappedLinkee).second) {
dirs.Forward.push_back(di);
}
if (direct_emitted.insert(mappedLinkee).second) {
dirs.Direct.emplace_back(di);
}
}
};
for (auto const& item : cli->GetItems()) {
if (item.Target) {
addLinkedTarget(item.Target, Forwarding::No);
+5
View File
@@ -108,6 +108,11 @@ TdiSourceInfo CollationInformationSources(cmGeneratorTarget const* gt,
if (fs_type != "CXX_MODULES"_s) {
continue;
}
// Synthetic (BMI-only) targets do not build private C++ modules.
if (tgt->IsSynthetic() &&
file_set->GetVisibility() == cmFileSetVisibility::Private) {
continue;
}
auto fileEntries = file_set->CompileFileEntries();
auto directoryEntries = file_set->CompileDirectoryEntries();
+101 -84
View File
@@ -5320,118 +5320,135 @@ bool cmGeneratorTarget::ApplyCXXStdTargets()
return true;
}
bool cmGeneratorTarget::DiscoverSyntheticTargets(cmSyntheticTargetCache& cache,
std::string const& config)
bool cmGeneratorTarget::DiscoverSyntheticTargets(
cmSyntheticTargetCache& cache, std::string const& config,
cmGeneratorTarget const* bmiConsumer)
{
std::vector<std::string> allConfigs =
this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
cmOptionalLinkImplementation impl;
this->ComputeLinkImplementationLibraries(config, impl, UseTo::Link);
cmCxxModuleUsageEffects usage(this);
if (!bmiConsumer) {
bmiConsumer = this;
}
cmCxxModuleUsageEffects usage(bmiConsumer);
auto& SyntheticDeps = this->Configs[config].SyntheticDeps;
for (auto const& entry : impl.Libraries) {
auto const* gt = entry.Target;
if (!gt || !gt->IsImported()) {
if (!gt || !gt->HaveCxx20ModuleSources()) {
continue;
}
if (gt->HaveCxx20ModuleSources()) {
cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_512);
constexpr size_t HASH_TRUNCATION = 12;
auto dirhash = hasher.HashString(
gt->GetLocalGenerator()->GetCurrentBinaryDirectory());
std::string safeName = gt->GetName();
cmSystemTools::ReplaceString(safeName, ":", "_");
auto targetIdent =
hasher.HashString(cmStrCat("@d_", dirhash, "@u_", usage.GetHash()));
std::string targetName =
cmStrCat(safeName, "@synth_", targetIdent.substr(0, HASH_TRUNCATION));
// Visual Studio generators do not currently support BMI-only compilation,
// so they can't generate synthetic targets. For consuming native modules,
// skip so that the native target is used directly. For imported modules,
// create the synth target anyway and diagnose later, in the VS generator.
if (!gt->IsImported() && this->GlobalGenerator->IsVisualStudio()) {
continue;
}
// Check the cache to see if this instance of the imported target has
// already been created.
auto cached = cache.CxxModuleTargets.find(targetName);
cmGeneratorTarget const* synthDep = nullptr;
if (cached == cache.CxxModuleTargets.end()) {
auto const* model = gt->Target;
auto* mf = gt->Makefile;
auto* lg = gt->GetLocalGenerator();
auto* tgt = mf->AddSynthesizedTarget(cmStateEnums::INTERFACE_LIBRARY,
targetName);
cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_512);
constexpr size_t HASH_TRUNCATION = 12;
auto dirhash =
hasher.HashString(gt->GetLocalGenerator()->GetCurrentBinaryDirectory());
std::string safeName = gt->GetName();
cmSystemTools::ReplaceString(safeName, ":", "_");
auto targetIdent =
hasher.HashString(cmStrCat("@d_", dirhash, "@u_", usage.GetHash()));
std::string targetName =
cmStrCat(safeName, "@synth_", targetIdent.substr(0, HASH_TRUNCATION));
// Copy relevant information from the existing IMPORTED target.
// Check the cache to see if this instance of the target has
// already been created.
auto cached = cache.CxxModuleTargets.find(targetName);
cmGeneratorTarget const* synthDep = nullptr;
if (cached == cache.CxxModuleTargets.end()) {
auto const* model = gt->Target;
auto* mf = gt->Makefile;
auto* lg = gt->GetLocalGenerator();
auto* tgt =
mf->AddSynthesizedTarget(cmStateEnums::INTERFACE_LIBRARY, targetName);
// Copy policies to the target.
tgt->CopyPolicyStatuses(model);
// Copy relevant information from the existing target.
// Copy file sets.
{
auto fsNames = model->GetAllFileSetNames();
for (auto const& fsName : fsNames) {
auto const* fs = model->GetFileSet(fsName);
if (!fs) {
mf->IssueMessage(MessageType::INTERNAL_ERROR,
cmStrCat("Failed to find file set named '",
fsName, "' on target '",
tgt->GetName(), '\''));
continue;
}
auto* newFs = tgt
->GetOrCreateFileSet(fs->GetName(), fs->GetType(),
fs->GetVisibility())
.first;
newFs->CopyEntries(fs);
// Copy policies to the target.
tgt->CopyPolicyStatuses(model);
// Copy file sets.
{
auto fsNames = model->GetAllFileSetNames();
for (auto const& fsName : fsNames) {
auto const* fs = model->GetFileSet(fsName);
if (!fs) {
mf->IssueMessage(MessageType::INTERNAL_ERROR,
cmStrCat("Failed to find file set named '",
fsName, "' on target '", tgt->GetName(),
'\''));
continue;
}
auto* newFs = tgt
->GetOrCreateFileSet(fs->GetName(), fs->GetType(),
fs->GetVisibility())
.first;
newFs->CopyEntries(fs);
}
// Copy imported C++ module properties.
tgt->CopyImportedCxxModulesEntries(model);
// Copy other properties which may affect the C++ module BMI
// generation.
tgt->CopyImportedCxxModulesProperties(model);
tgt->AddLinkLibrary(*mf,
cmStrCat("$<COMPILE_ONLY:", model->GetName(), '>'),
GENERAL_LibraryType);
// Apply usage requirements to the target.
usage.ApplyToTarget(tgt);
// Create the generator target and attach it to the local generator.
auto gtp = cm::make_unique<cmGeneratorTarget>(tgt, lg);
synthDep = gtp.get();
cache.CxxModuleTargets[targetName] = synthDep;
// See `localGen->ComputeTargetCompileFeatures()` call in
// `cmGlobalGenerator::Compute` for where non-synthetic targets resolve
// this.
for (auto const& innerConfig : allConfigs) {
gtp->ComputeCompileFeatures(innerConfig);
}
// See `cmGlobalGenerator::ApplyCXXStdTargets` in
// `cmGlobalGenerator::Compute` for non-synthetic target resolutions.
if (!gtp->ApplyCXXStdTargets()) {
return false;
}
gtp->DiscoverSyntheticTargets(cache, config);
lg->AddGeneratorTarget(std::move(gtp));
} else {
synthDep = cached->second;
}
SyntheticDeps[gt].push_back(synthDep);
// Copy C++ module properties.
tgt->CopyCxxModulesEntries(model);
// Copy other properties which may affect the C++ module BMI
// generation.
tgt->CopyCxxModulesProperties(model);
tgt->AddLinkLibrary(*mf,
cmStrCat("$<COMPILE_ONLY:", model->GetName(), '>'),
GENERAL_LibraryType);
// Apply usage requirements to the target.
usage.ApplyToTarget(tgt);
// Create the generator target and attach it to the local generator.
auto gtp = cm::make_unique<cmGeneratorTarget>(tgt, lg);
synthDep = gtp.get();
cache.CxxModuleTargets[targetName] = synthDep;
// See `localGen->ComputeTargetCompileFeatures()` call in
// `cmGlobalGenerator::Compute` for where non-synthetic targets resolve
// this.
for (auto const& innerConfig : allConfigs) {
gtp->ComputeCompileFeatures(innerConfig);
}
// See `cmGlobalGenerator::ApplyCXXStdTargets` in
// `cmGlobalGenerator::Compute` for non-synthetic target resolutions.
if (!gtp->ApplyCXXStdTargets()) {
return false;
}
gtp->DiscoverSyntheticTargets(cache, config, bmiConsumer);
lg->AddGeneratorTarget(std::move(gtp));
} else {
synthDep = cached->second;
}
SyntheticDeps[gt].push_back(synthDep);
}
return true;
}
cmGeneratorTarget::SyntheticDepsMap const& cmGeneratorTarget::GetSyntheticDeps(
std::string const& config) const
{
return this->Configs[config].SyntheticDeps;
}
bool cmGeneratorTarget::HasPackageReferences() const
{
return this->IsInBuildSystem() &&
+7 -2
View File
@@ -1118,8 +1118,13 @@ public:
std::string GetImportedXcFrameworkPath(std::string const& config) const;
bool ApplyCXXStdTargets();
bool DiscoverSyntheticTargets(cmSyntheticTargetCache& cache,
std::string const& config);
bool DiscoverSyntheticTargets(
cmSyntheticTargetCache& cache, std::string const& config,
cmGeneratorTarget const* bmiConsumer = nullptr);
using SyntheticDepsMap =
std::map<cmGeneratorTarget const*, std::vector<cmGeneratorTarget const*>>;
SyntheticDepsMap const& GetSyntheticDeps(std::string const& config) const;
class CustomTransitiveProperty : public TransitiveProperty
{
+47 -4
View File
@@ -2587,8 +2587,8 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
std::string const& module_dir,
std::vector<std::string> const& linked_target_dirs,
std::vector<std::string> const& forward_modules_from_target_dirs,
std::string const& arg_lang, std::string const& arg_modmapfmt,
cmCxxModuleExportInfo const& export_info)
std::string const& native_target_dir, std::string const& arg_lang,
std::string const& arg_modmapfmt, cmCxxModuleExportInfo const& export_info)
{
// Setup path conversions.
{
@@ -2748,6 +2748,47 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
}
}
// If this is a synthetic target for a non-imported target, read PRIVATE
// module info from the native target
if (!native_target_dir.empty()) {
std::string const modules_info_path =
cmStrCat(native_target_dir, '/', arg_lang, "Modules.json");
Json::Value native_modules_info;
cmsys::ifstream modules_file(modules_info_path.c_str(),
std::ios::in | std::ios::binary);
if (!modules_file) {
cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to open ",
modules_info_path,
" for module information"));
return false;
}
Json::Reader reader;
if (!reader.parse(modules_file, native_modules_info, false)) {
cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
modules_info_path,
reader.getFormattedErrorMessages()));
return false;
}
if (native_modules_info.isObject()) {
Json::Value const& native_target_modules =
native_modules_info["modules"];
if (native_target_modules.isObject()) {
for (auto i = native_target_modules.begin();
i != native_target_modules.end(); ++i) {
Json::Value const& visible_module = *i;
if (visible_module.isObject()) {
auto is_private = visible_module["is-private"].asBool();
// Only add private modules since others are discovered by the
// synthetic target's own scan rules
if (is_private) {
target_modules[i.key().asString()] = visible_module;
}
}
}
}
}
}
cmGeneratedFileStream ddf(arg_dd);
ddf << "ninja_dyndep_version = 1.0\n";
@@ -3041,6 +3082,7 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
tdi_forward_modules_from_target_dir.asString());
}
}
std::string const native_target_dir = tdi["native-target-dir"].asString();
std::string const compilerId = tdi["compiler-id"].asString();
std::string const simulateId = tdi["compiler-simulate-id"].asString();
std::string const compilerFrontendVariant =
@@ -3064,8 +3106,9 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
# endif
return gg.WriteDyndepFile(dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld,
arg_dd, arg_ddis, module_dir, linked_target_dirs,
forward_modules_from_target_dirs, arg_lang,
arg_modmapfmt, *export_info)
forward_modules_from_target_dirs,
native_target_dir, arg_lang, arg_modmapfmt,
*export_info)
? 0
: 1;
}
+2 -1
View File
@@ -433,7 +433,8 @@ public:
std::string const& module_dir,
std::vector<std::string> const& linked_target_dirs,
std::vector<std::string> const& forward_modules_from_target_dirs,
std::string const& arg_lang, std::string const& arg_modmapfmt,
std::string const& native_target_dir, std::string const& arg_lang,
std::string const& arg_modmapfmt,
cmCxxModuleExportInfo const& export_info);
virtual std::string BuildAlias(std::string const& alias,
+33
View File
@@ -1228,6 +1228,23 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
this->WriteTargetDependInfo(language, config);
// Non-imported synthetic targets read module info from their native target
// Add as implicit dependency.
if (this->GeneratorTarget->IsSynthetic()) {
if (cmGeneratorTarget const* native_gt =
this->LocalGenerator->FindGeneratorTargetToUse(
this->GeneratorTarget->Target->GetTemplateName())) {
if (!native_gt->IsImported()) {
std::string native_dir = native_gt->GetSupportDirectory();
if (this->GetGlobalGenerator()->IsMultiConfig()) {
native_dir = cmStrCat(native_dir, '/', config);
}
build.ImplicitDeps.emplace_back(this->ConvertToNinjaPath(
cmStrCat(native_dir, '/', language, "Modules.json")));
}
}
}
auto const linked_directories =
this->GetLinkedTargetDirectories(language, config);
for (std::string const& l : linked_directories.Direct) {
@@ -2155,6 +2172,22 @@ void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
tdi_forward_modules_from_target_dirs.append(l);
}
// Record the native target support directory for non-imported synthetic
// targets
if (this->GeneratorTarget->IsSynthetic()) {
if (cmGeneratorTarget* nativeGT =
this->LocalGenerator->FindGeneratorTargetToUse(
this->GeneratorTarget->Target->GetTemplateName())) {
if (!nativeGT->IsImported()) {
std::string nativeDir = nativeGT->GetSupportDirectory();
if (this->GetGlobalGenerator()->IsMultiConfig()) {
nativeDir = cmStrCat(nativeDir, '/', config);
}
tdi["native-target-dir"] = nativeDir;
}
}
}
cmDyndepGeneratorCallbacks cb;
cb.ObjectFilePath = [this](cmSourceFile const* sf, std::string const& cnf) {
return this->GetObjectFilePath(sf, cnf);
+33 -18
View File
@@ -1718,37 +1718,52 @@ void cmTarget::CopyPolicyStatuses(cmTarget const* tgt)
assert(!this->IsNormal());
// Imported targets cannot be the target of a copy.
assert(!this->IsImported());
// Only imported targets can be the source of a copy.
assert(tgt->IsImported());
// Only imported or normal targets can be the source of a copy.
assert(tgt->IsImported() || tgt->IsNormal());
this->impl->PolicyMap = tgt->impl->PolicyMap;
this->impl->TemplateTarget = tgt;
}
void cmTarget::CopyImportedCxxModulesEntries(cmTarget const* tgt)
void cmTarget::CopyCxxModulesEntries(cmTarget const* tgt)
{
// Normal targets cannot be the target of a copy.
assert(!this->IsNormal());
// Imported targets cannot be the target of a copy.
assert(!this->IsImported());
// Only imported targets can be the source of a copy.
assert(tgt->IsImported());
// Only imported or normal targets can be the source of a copy.
assert(tgt->IsImported() || tgt->IsNormal());
this->impl->IncludeDirectories.Entries.clear();
this->impl->IncludeDirectories.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesIncludeDirectories.Entries));
this->impl->CompileDefinitions.Entries.clear();
this->impl->CompileDefinitions.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesCompileDefinitions.Entries));
this->impl->CompileFeatures.Entries.clear();
this->impl->CompileFeatures.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesCompileFeatures.Entries));
this->impl->CompileOptions.Entries.clear();
this->impl->CompileOptions.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesCompileOptions.Entries));
this->impl->LinkLibraries.Entries.clear();
this->impl->LinkLibraries.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesLinkLibraries.Entries));
if (tgt->IsImported()) {
this->impl->IncludeDirectories.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesIncludeDirectories.Entries));
this->impl->CompileDefinitions.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesCompileDefinitions.Entries));
this->impl->CompileFeatures.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesCompileFeatures.Entries));
this->impl->CompileOptions.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesCompileOptions.Entries));
this->impl->LinkLibraries.CopyFromEntries(
cmMakeRange(tgt->impl->ImportedCxxModulesLinkLibraries.Entries));
} else {
this->impl->IncludeDirectories.CopyFromEntries(
cmMakeRange(tgt->impl->IncludeDirectories.Entries));
this->impl->CompileDefinitions.CopyFromEntries(
cmMakeRange(tgt->impl->CompileDefinitions.Entries));
this->impl->CompileFeatures.CopyFromEntries(
cmMakeRange(tgt->impl->CompileFeatures.Entries));
this->impl->CompileOptions.CopyFromEntries(
cmMakeRange(tgt->impl->CompileOptions.Entries));
this->impl->LinkLibraries.CopyFromEntries(
cmMakeRange(tgt->impl->LinkLibraries.Entries));
}
// Copy the C++ module fileset entries from `tgt`'s `INTERFACE` to this
// target's `PRIVATE`.
@@ -1757,14 +1772,14 @@ void cmTarget::CopyImportedCxxModulesEntries(cmTarget const* tgt)
tgt->impl->CxxModulesFileSets.InterfaceEntries.Entries;
}
void cmTarget::CopyImportedCxxModulesProperties(cmTarget const* tgt)
void cmTarget::CopyCxxModulesProperties(cmTarget const* tgt)
{
// Normal targets cannot be the target of a copy.
assert(!this->IsNormal());
// Imported targets cannot be the target of a copy.
assert(!this->IsImported());
// Only imported targets can be the source of a copy.
assert(tgt->IsImported());
// Only imported or normal targets can be the source of a copy.
assert(tgt->IsImported() || tgt->IsNormal());
// The list of properties that are relevant here include:
// - compilation-specific properties for any language or platform
+2 -2
View File
@@ -319,8 +319,8 @@ public:
cmBTStringRange GetLinkInterfaceDirectExcludeEntries() const;
void CopyPolicyStatuses(cmTarget const* tgt);
void CopyImportedCxxModulesEntries(cmTarget const* tgt);
void CopyImportedCxxModulesProperties(cmTarget const* tgt);
void CopyCxxModulesEntries(cmTarget const* tgt);
void CopyCxxModulesProperties(cmTarget const* tgt);
cmBTStringRange GetHeaderSetsEntries() const;
cmBTStringRange GetCxxModuleSetsEntries() const;
@@ -251,6 +251,7 @@ endif ()
# Tests which use named modules in shared libraries.
if ("shared" IN_LIST CMake_TEST_MODULE_COMPILATION)
run_cxx_module_test(library library-shared -DBUILD_SHARED_LIBS=ON)
run_cxx_module_test(shared-library-symbol-visibility)
endif ()
# Tests which use partitions.
@@ -1,12 +1,27 @@
if (RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(dep_modules_json_path "CMakeFiles/depchain_modules_json_file.dir/Debug/CXX.dd")
set(modules_json_path "CMakeFiles/depchain_with_modules_json_file.dir/Debug/CXXModules.json")
else ()
set(dep_modules_json_path "CMakeFiles/depchain_modules_json_file.dir/CXX.dd")
set(modules_json_path "CMakeFiles/depchain_with_modules_json_file.dir/CXXModules.json")
file(GLOB synth_dirs
"${RunCMake_TEST_BINARY_DIR}/CMakeFiles/depchain_with_modules_json_file@synth_*.dir")
list(LENGTH synth_dirs synth_dirs_len)
if (NOT synth_dirs_len EQUAL 1)
list(APPEND RunCMake_TEST_FAILED
"Expected exactly one synthetic target for consuming 'depchain_with_modules_json_file' but found ${synth_dirs_len}: ${synth_dirs}")
endif ()
if ("${RunCMake_TEST_BINARY_DIR}/${modules_json_path}" IS_NEWER_THAN "${RunCMake_TEST_BINARY_DIR}/${dep_modules_json_path}")
list(GET synth_dirs 0 synth_dir)
if (RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(dep_modules_json_path "CMakeFiles/depchain_modules_json_file.dir/Debug/CXX.dd")
set(modules_json_path "${synth_dir}/Debug/CXXModules.json")
else ()
set(dep_modules_json_path "CMakeFiles/depchain_modules_json_file.dir/CXX.dd")
set(modules_json_path "${synth_dir}/CXXModules.json")
endif ()
if ("${modules_json_path}" IS_NEWER_THAN "${RunCMake_TEST_BINARY_DIR}/${dep_modules_json_path}")
cmake_path(RELATIVE_PATH modules_json_path
BASE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
list(APPEND RunCMake_TEST_FAILED
"Object '${dep_modules_json_path}' should have recompiled if '${modules_json_path}' changed.")
endif ()
@@ -1,7 +1,17 @@
file(GLOB synth_dirs
"${RunCMake_TEST_BINARY_DIR}/CMakeFiles/depchain_with_modules_json_file@synth_*.dir")
list(LENGTH synth_dirs synth_dirs_len)
if (NOT synth_dirs_len EQUAL 1)
return()
endif()
list(GET synth_dirs 0 synth_dir)
if (RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(modules_json_path "CMakeFiles/depchain_with_modules_json_file.dir/Debug/CXXModules.json")
set(modules_json_path "${synth_dir}/Debug/CXXModules.json")
else ()
set(modules_json_path "CMakeFiles/depchain_with_modules_json_file.dir/CXXModules.json")
set(modules_json_path "${synth_dir}/CXXModules.json")
endif ()
file(TOUCH_NOCREATE "${RunCMake_TEST_BINARY_DIR}/${modules_json_path}")
file(TOUCH_NOCREATE "${modules_json_path}")
@@ -18,7 +18,9 @@ target_sources(library
BASE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}"
FILES
importable.cxx)
importable.ixx
PRIVATE
importable.cxx)
target_compile_features(library PUBLIC cxx_std_20)
add_executable(exe)
@@ -1,8 +1,6 @@
export module importable;
module importable;
#include "library_export.h"
export LIBRARY_EXPORT int from_import()
int from_import()
{
return 0;
}
@@ -0,0 +1,5 @@
export module importable;
#include "library_export.h"
export LIBRARY_EXPORT int from_import();
@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.24...3.28)
project(cxx_modules_library CXX)
include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
include(GenerateExportHeader)
add_library(library SHARED)
generate_export_header(library)
target_sources(library
PUBLIC
FILE_SET HEADERS
BASE_DIRS
"${CMAKE_CURRENT_BINARY_DIR}"
FILES
"${CMAKE_CURRENT_BINARY_DIR}/library_export.h"
FILE_SET CXX_MODULES
BASE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}"
FILES
importable.ixx
PRIVATE
importable.cxx)
target_compile_features(library PUBLIC cxx_std_20)
target_compile_definitions(library INTERFACE CHECK_IMPORT_INTERFACE)
add_executable(exe)
target_link_libraries(exe PRIVATE library)
target_sources(exe
PRIVATE
main.cxx)
add_test(NAME exe COMMAND exe)
@@ -0,0 +1,6 @@
module importable;
int from_import()
{
return 0;
}
@@ -0,0 +1,15 @@
export module importable;
#include "library_export.h"
#ifdef CHECK_IMPORT_INTERFACE
# ifdef library_EXPORTS
# error "library_EXPORTS defined but should NOT be defined when importing module"
# endif
#else
# ifndef library_EXPORTS
# error "library_EXPORTS NOT defined but should be defined when building module"
# endif
#endif
export LIBRARY_EXPORT int from_import();
@@ -0,0 +1,6 @@
import importable;
int main(int argc, char* argv[])
{
return from_import();
}