diff options
Diffstat (limited to 'Source/cmDyndepCollation.cxx')
-rw-r--r-- | Source/cmDyndepCollation.cxx | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/Source/cmDyndepCollation.cxx b/Source/cmDyndepCollation.cxx new file mode 100644 index 0000000..2827659 --- /dev/null +++ b/Source/cmDyndepCollation.cxx @@ -0,0 +1,652 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmDyndepCollation.h" + +#include <algorithm> +#include <map> +#include <ostream> +#include <set> +#include <utility> +#include <vector> + +#include <cm/memory> +#include <cm/string_view> +#include <cmext/string_view> + +#include <cm3p/json/value.h> + +#include "cmExportBuildFileGenerator.h" +#include "cmExportSet.h" +#include "cmFileSet.h" +#include "cmGeneratedFileStream.h" +#include "cmGeneratorExpression.h" // IWYU pragma: keep +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" +#include "cmInstallCxxModuleBmiGenerator.h" +#include "cmInstallExportGenerator.h" +#include "cmInstallFileSetGenerator.h" +#include "cmInstallGenerator.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmOutputConverter.h" +#include "cmScanDepFormat.h" +#include "cmSourceFile.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmTarget.h" +#include "cmTargetExport.h" + +namespace { + +Json::Value CollationInformationCxxModules( + cmGeneratorTarget const* gt, std::string const& config, + cmDyndepGeneratorCallbacks const& cb) +{ + cmTarget const* tgt = gt->Target; + auto all_file_sets = tgt->GetAllFileSetNames(); + Json::Value tdi_cxx_module_info = Json::objectValue; + for (auto const& file_set_name : all_file_sets) { + auto const* file_set = tgt->GetFileSet(file_set_name); + if (!file_set) { + gt->Makefile->IssueMessage(MessageType::INTERNAL_ERROR, + cmStrCat("Target \"", tgt->GetName(), + "\" is tracked to have file set \"", + file_set_name, + "\", but it was not found.")); + continue; + } + auto fs_type = file_set->GetType(); + // We only care about C++ module sources here. + if (fs_type != "CXX_MODULES"_s) { + continue; + } + + auto fileEntries = file_set->CompileFileEntries(); + auto directoryEntries = file_set->CompileDirectoryEntries(); + + auto directories = file_set->EvaluateDirectoryEntries( + directoryEntries, gt->LocalGenerator, config, gt); + std::map<std::string, std::vector<std::string>> files_per_dirs; + for (auto const& entry : fileEntries) { + file_set->EvaluateFileEntry(directories, files_per_dirs, entry, + gt->LocalGenerator, config, gt); + } + + std::map<std::string, cmSourceFile const*> sf_map; + { + std::vector<cmSourceFile const*> objectSources; + gt->GetObjectSources(objectSources, config); + for (auto const* sf : objectSources) { + auto full_path = sf->GetFullPath(); + if (full_path.empty()) { + gt->Makefile->IssueMessage( + MessageType::INTERNAL_ERROR, + cmStrCat("Target \"", tgt->GetName(), + "\" has a full path-less source file.")); + continue; + } + sf_map[full_path] = sf; + } + } + + Json::Value fs_dest = Json::nullValue; + for (auto const& ig : gt->Makefile->GetInstallGenerators()) { + if (auto const* fsg = + dynamic_cast<cmInstallFileSetGenerator const*>(ig.get())) { + if (fsg->GetTarget() == gt && fsg->GetFileSet() == file_set) { + fs_dest = fsg->GetDestination(config); + continue; + } + } + } + + for (auto const& files_per_dir : files_per_dirs) { + for (auto const& file : files_per_dir.second) { + auto lookup = sf_map.find(file); + if (lookup == sf_map.end()) { + gt->Makefile->IssueMessage( + MessageType::INTERNAL_ERROR, + cmStrCat("Target \"", tgt->GetName(), "\" has source file \"", + file, + R"(" which is not in any of its "FILE_SET BASE_DIRS".)")); + continue; + } + + auto const* sf = lookup->second; + + if (!sf) { + gt->Makefile->IssueMessage( + MessageType::INTERNAL_ERROR, + cmStrCat("Target \"", tgt->GetName(), "\" has source file \"", + file, "\" which has not been tracked properly.")); + continue; + } + + auto obj_path = cb.ObjectFilePath(sf, config); + Json::Value& tdi_module_info = tdi_cxx_module_info[obj_path] = + Json::objectValue; + + tdi_module_info["source"] = file; + tdi_module_info["relative-directory"] = files_per_dir.first; + tdi_module_info["name"] = file_set->GetName(); + tdi_module_info["type"] = file_set->GetType(); + tdi_module_info["visibility"] = + std::string(cmFileSetVisibilityToName(file_set->GetVisibility())); + tdi_module_info["destination"] = fs_dest; + } + } + } + + return tdi_cxx_module_info; +} + +Json::Value CollationInformationBmiInstallation(cmGeneratorTarget const* gt, + std::string const& config) +{ + cmInstallCxxModuleBmiGenerator const* bmi_gen = nullptr; + for (auto const& ig : gt->Makefile->GetInstallGenerators()) { + if (auto const* bmig = + dynamic_cast<cmInstallCxxModuleBmiGenerator const*>(ig.get())) { + if (bmig->GetTarget() == gt) { + bmi_gen = bmig; + continue; + } + } + } + if (bmi_gen) { + Json::Value tdi_bmi_info = Json::objectValue; + + tdi_bmi_info["permissions"] = bmi_gen->GetFilePermissions(); + tdi_bmi_info["destination"] = bmi_gen->GetDestination(config); + const char* msg_level = ""; + switch (bmi_gen->GetMessageLevel()) { + case cmInstallGenerator::MessageDefault: + break; + case cmInstallGenerator::MessageAlways: + msg_level = "MESSAGE_ALWAYS"; + break; + case cmInstallGenerator::MessageLazy: + msg_level = "MESSAGE_LAZY"; + break; + case cmInstallGenerator::MessageNever: + msg_level = "MESSAGE_NEVER"; + break; + } + tdi_bmi_info["message-level"] = msg_level; + tdi_bmi_info["script-location"] = bmi_gen->GetScriptLocation(config); + + return tdi_bmi_info; + } + return Json::nullValue; +} + +Json::Value CollationInformationExports(cmGeneratorTarget const* gt) +{ + Json::Value tdi_exports = Json::arrayValue; + std::string export_name = gt->GetExportName(); + + auto const& all_install_exports = gt->GetGlobalGenerator()->GetExportSets(); + for (auto const& exp : all_install_exports) { + // Ignore exports sets which are not for this target. + auto const& targets = exp.second.GetTargetExports(); + auto tgt_export = + std::find_if(targets.begin(), targets.end(), + [gt](std::unique_ptr<cmTargetExport> const& te) { + return te->Target == gt; + }); + if (tgt_export == targets.end()) { + continue; + } + + auto const* installs = exp.second.GetInstallations(); + for (auto const* install : *installs) { + Json::Value tdi_export_info = Json::objectValue; + + auto const& ns = install->GetNamespace(); + auto const& dest = install->GetDestination(); + auto const& cxxm_dir = install->GetCxxModuleDirectory(); + auto const& export_prefix = install->GetTempDir(); + + tdi_export_info["namespace"] = ns; + tdi_export_info["export-name"] = export_name; + tdi_export_info["destination"] = dest; + tdi_export_info["cxx-module-info-dir"] = cxxm_dir; + tdi_export_info["export-prefix"] = export_prefix; + tdi_export_info["install"] = true; + + tdi_exports.append(tdi_export_info); + } + } + + auto const& all_build_exports = gt->Makefile->GetExportBuildFileGenerators(); + for (auto const& exp : all_build_exports) { + std::vector<std::string> targets; + exp->GetTargets(targets); + + // Ignore exports sets which are not for this target. + auto const& name = gt->GetName(); + bool has_current_target = + std::any_of(targets.begin(), targets.end(), + [name](std::string const& tname) { return tname == name; }); + if (!has_current_target) { + continue; + } + + Json::Value tdi_export_info = Json::objectValue; + + auto const& ns = exp->GetNamespace(); + auto const& main_fn = exp->GetMainExportFileName(); + auto const& cxxm_dir = exp->GetCxxModuleDirectory(); + auto dest = cmsys::SystemTools::GetParentDirectory(main_fn); + auto const& export_prefix = + cmSystemTools::GetFilenamePath(exp->GetMainExportFileName()); + + tdi_export_info["namespace"] = ns; + tdi_export_info["export-name"] = export_name; + tdi_export_info["destination"] = dest; + tdi_export_info["cxx-module-info-dir"] = cxxm_dir; + tdi_export_info["export-prefix"] = export_prefix; + tdi_export_info["install"] = false; + + tdi_exports.append(tdi_export_info); + } + + return tdi_exports; +} +} + +void cmDyndepCollation::AddCollationInformation( + Json::Value& tdi, cmGeneratorTarget const* gt, std::string const& config, + cmDyndepGeneratorCallbacks const& cb) +{ + tdi["cxx-modules"] = CollationInformationCxxModules(gt, config, cb); + tdi["bmi-installation"] = CollationInformationBmiInstallation(gt, config); + tdi["exports"] = CollationInformationExports(gt); + tdi["config"] = config; +} + +struct CxxModuleFileSet +{ + std::string Name; + std::string RelativeDirectory; + std::string SourcePath; + std::string Type; + cmFileSetVisibility Visibility; + cm::optional<std::string> Destination; +}; + +struct CxxModuleBmiInstall +{ + std::string Component; + std::string Destination; + bool ExcludeFromAll; + bool Optional; + std::string Permissions; + std::string MessageLevel; + std::string ScriptLocation; +}; + +struct CxxModuleExport +{ + std::string Name; + std::string Destination; + std::string Prefix; + std::string CxxModuleInfoDir; + std::string Namespace; + bool Install; +}; + +struct cmCxxModuleExportInfo +{ + std::map<std::string, CxxModuleFileSet> ObjectToFileSet; + cm::optional<CxxModuleBmiInstall> BmiInstallation; + std::vector<CxxModuleExport> Exports; + std::string Config; +}; + +void cmCxxModuleExportInfoDeleter::operator()(cmCxxModuleExportInfo* ei) const +{ + delete ei; +} + +std::unique_ptr<cmCxxModuleExportInfo, cmCxxModuleExportInfoDeleter> +cmDyndepCollation::ParseExportInfo(Json::Value const& tdi) +{ + auto export_info = + std::unique_ptr<cmCxxModuleExportInfo, cmCxxModuleExportInfoDeleter>( + new cmCxxModuleExportInfo); + + export_info->Config = tdi["config"].asString(); + if (export_info->Config.empty()) { + export_info->Config = "noconfig"; + } + Json::Value const& tdi_exports = tdi["exports"]; + if (tdi_exports.isArray()) { + for (auto const& tdi_export : tdi_exports) { + CxxModuleExport exp; + exp.Install = tdi_export["install"].asBool(); + exp.Name = tdi_export["export-name"].asString(); + exp.Destination = tdi_export["destination"].asString(); + exp.Prefix = tdi_export["export-prefix"].asString(); + exp.CxxModuleInfoDir = tdi_export["cxx-module-info-dir"].asString(); + exp.Namespace = tdi_export["namespace"].asString(); + + export_info->Exports.push_back(exp); + } + } + auto const& bmi_installation = tdi["bmi-installation"]; + if (bmi_installation.isObject()) { + CxxModuleBmiInstall bmi_install; + + bmi_install.Component = bmi_installation["component"].asString(); + bmi_install.Destination = bmi_installation["destination"].asString(); + bmi_install.ExcludeFromAll = bmi_installation["exclude-from-all"].asBool(); + bmi_install.Optional = bmi_installation["optional"].asBool(); + bmi_install.Permissions = bmi_installation["permissions"].asString(); + bmi_install.MessageLevel = bmi_installation["message-level"].asString(); + bmi_install.ScriptLocation = + bmi_installation["script-location"].asString(); + + export_info->BmiInstallation = bmi_install; + } + Json::Value const& tdi_cxx_modules = tdi["cxx-modules"]; + if (tdi_cxx_modules.isObject()) { + for (auto i = tdi_cxx_modules.begin(); i != tdi_cxx_modules.end(); ++i) { + CxxModuleFileSet& fsi = export_info->ObjectToFileSet[i.key().asString()]; + auto const& tdi_cxx_module_info = *i; + fsi.Name = tdi_cxx_module_info["name"].asString(); + fsi.RelativeDirectory = + tdi_cxx_module_info["relative-directory"].asString(); + fsi.SourcePath = tdi_cxx_module_info["source"].asString(); + fsi.Type = tdi_cxx_module_info["type"].asString(); + fsi.Visibility = cmFileSetVisibilityFromName( + tdi_cxx_module_info["visibility"].asString(), nullptr); + auto const& tdi_fs_dest = tdi_cxx_module_info["destination"]; + if (tdi_fs_dest.isString()) { + fsi.Destination = tdi_fs_dest.asString(); + } + } + } + + return export_info; +} + +bool cmDyndepCollation::WriteDyndepMetadata( + std::string const& lang, std::vector<cmScanDepInfo> const& objects, + cmCxxModuleExportInfo const& export_info, + cmDyndepMetadataCallbacks const& cb) +{ + // Only C++ supports any of the file-set or BMI installation considered + // below. + if (lang != "CXX"_s) { + return true; + } + + bool result = true; + + // Prepare the export information blocks. + std::string const config_upper = + cmSystemTools::UpperCase(export_info.Config); + std::vector< + std::pair<std::unique_ptr<cmGeneratedFileStream>, CxxModuleExport const*>> + exports; + for (auto const& exp : export_info.Exports) { + std::unique_ptr<cmGeneratedFileStream> properties; + + std::string const export_dir = + cmStrCat(exp.Prefix, '/', exp.CxxModuleInfoDir, '/'); + std::string const property_file_path = cmStrCat( + export_dir, "target-", exp.Name, '-', export_info.Config, ".cmake"); + properties = cm::make_unique<cmGeneratedFileStream>(property_file_path); + + // Set up the preamble. + *properties << "set_property(TARGET \"" << exp.Namespace << exp.Name + << "\"\n" + << " PROPERTY IMPORTED_CXX_MODULES_" << config_upper << '\n'; + + exports.emplace_back(std::move(properties), &exp); + } + + std::unique_ptr<cmGeneratedFileStream> bmi_install_script; + if (export_info.BmiInstallation) { + bmi_install_script = cm::make_unique<cmGeneratedFileStream>( + export_info.BmiInstallation->ScriptLocation); + } + + auto cmEscape = [](cm::string_view str) { + return cmOutputConverter::EscapeForCMake( + str, cmOutputConverter::WrapQuotes::NoWrap); + }; + auto install_destination = + [&cmEscape](std::string const& dest) -> std::pair<bool, std::string> { + if (cmSystemTools::FileIsFullPath(dest)) { + return std::make_pair(true, cmEscape(dest)); + } + return std::make_pair(false, + cmStrCat("${_IMPORT_PREFIX}/", cmEscape(dest))); + }; + + // public/private requirement tracking. + std::set<std::string> private_modules; + std::map<std::string, std::set<std::string>> public_source_requires; + + for (cmScanDepInfo const& object : objects) { + // Convert to forward slashes. + auto output_path = object.PrimaryOutput; +#ifdef _WIN32 + cmSystemTools::ConvertToUnixSlashes(output_path); +#endif + // Find the fileset for this object. + auto fileset_info_itr = export_info.ObjectToFileSet.find(output_path); + bool const has_provides = !object.Provides.empty(); + if (fileset_info_itr == export_info.ObjectToFileSet.end()) { + // If it provides anything, it should have a `CXX_MODULES` or + // `CXX_MODULE_INTERNAL_PARTITIONS` type and be present. + if (has_provides) { + // Take the first module provided to provide context. + auto const& provides = object.Provides[0]; + char const* ok_types = "`CXX_MODULES`"; + if (provides.LogicalName.find(':') != std::string::npos) { + ok_types = "`CXX_MODULES` (or `CXX_MODULE_INTERNAL_PARTITIONS` if " + "it is not `export`ed)"; + } + cmSystemTools::Error(cmStrCat( + "Output ", object.PrimaryOutput, " provides the `", + provides.LogicalName, + "` module but it is not found in a `FILE_SET` of type ", ok_types)); + result = false; + } + + // This object file does not provide anything, so nothing more needs to + // be done. + continue; + } + + auto const& file_set = fileset_info_itr->second; + + // Verify the fileset type for the object. + if (file_set.Type == "CXX_MODULES"_s) { + if (!has_provides) { + cmSystemTools::Error( + cmStrCat("Output ", object.PrimaryOutput, + " is of type `CXX_MODULES` but does not provide a module")); + result = false; + continue; + } + } else if (file_set.Type == "CXX_MODULE_INTERNAL_PARTITIONS"_s) { + if (!has_provides) { + cmSystemTools::Error( + cmStrCat("Source ", file_set.SourcePath, + " is of type `CXX_MODULE_INTERNAL_PARTITIONS` but does not " + "provide a module")); + result = false; + continue; + } + auto const& provides = object.Provides[0]; + if (provides.LogicalName.find(':') == std::string::npos) { + cmSystemTools::Error( + cmStrCat("Source ", file_set.SourcePath, + " is of type `CXX_MODULE_INTERNAL_PARTITIONS` but does not " + "provide a module partition")); + result = false; + continue; + } + } else if (file_set.Type == "CXX_MODULE_HEADERS"_s) { + // TODO. + } else { + if (has_provides) { + auto const& provides = object.Provides[0]; + char const* ok_types = "`CXX_MODULES`"; + if (provides.LogicalName.find(':') != std::string::npos) { + ok_types = "`CXX_MODULES` (or `CXX_MODULE_INTERNAL_PARTITIONS` if " + "it is not `export`ed)"; + } + cmSystemTools::Error( + cmStrCat("Source ", file_set.SourcePath, " provides the `", + provides.LogicalName, "` C++ module but is of type `", + file_set.Type, "` module but must be of type ", ok_types)); + result = false; + } + + // Not a C++ module; ignore. + continue; + } + + if (!cmFileSetVisibilityIsForInterface(file_set.Visibility)) { + // Nothing needs to be conveyed about non-`PUBLIC` modules. + for (auto const& p : object.Provides) { + private_modules.insert(p.LogicalName); + } + continue; + } + + // The module is public. Record what it directly requires. + { + auto& reqs = public_source_requires[file_set.SourcePath]; + for (auto const& r : object.Requires) { + reqs.insert(r.LogicalName); + } + } + + // Write out properties and install rules for any exports. + for (auto const& p : object.Provides) { + bool bmi_dest_is_abs = false; + std::string bmi_destination; + if (export_info.BmiInstallation) { + auto dest = + install_destination(export_info.BmiInstallation->Destination); + bmi_dest_is_abs = dest.first; + bmi_destination = cmStrCat(dest.second, '/'); + } + + std::string install_bmi_path; + std::string build_bmi_path; + auto m = cb.ModuleFile(p.LogicalName); + if (m) { + install_bmi_path = cmStrCat( + bmi_destination, cmEscape(cmSystemTools::GetFilenameName(*m))); + build_bmi_path = cmEscape(*m); + } + + for (auto const& exp : exports) { + std::string iface_source; + if (exp.second->Install && file_set.Destination) { + auto dest = install_destination(*file_set.Destination); + iface_source = cmStrCat( + dest.second, '/', cmEscape(file_set.RelativeDirectory), + cmEscape(cmSystemTools::GetFilenameName(file_set.SourcePath))); + } else { + iface_source = cmEscape(file_set.SourcePath); + } + + std::string bmi_path; + if (exp.second->Install && export_info.BmiInstallation) { + bmi_path = install_bmi_path; + } else if (!exp.second->Install) { + bmi_path = build_bmi_path; + } + + if (iface_source.empty()) { + // No destination for the C++ module source; ignore this property + // value. + continue; + } + + *exp.first << " \"" << cmEscape(p.LogicalName) << '=' + << iface_source; + if (!bmi_path.empty()) { + *exp.first << ',' << bmi_path; + } + *exp.first << "\"\n"; + } + + if (bmi_install_script) { + auto const& bmi_install = *export_info.BmiInstallation; + + *bmi_install_script << "if (CMAKE_INSTALL_COMPONENT STREQUAL \"" + << cmEscape(bmi_install.Component) << '\"'; + if (!bmi_install.ExcludeFromAll) { + *bmi_install_script << " OR NOT CMAKE_INSTALL_COMPONENT"; + } + *bmi_install_script << ")\n"; + *bmi_install_script << " file(INSTALL\n" + " DESTINATION \""; + if (!bmi_dest_is_abs) { + *bmi_install_script << "${CMAKE_INSTALL_PREFIX}/"; + } + *bmi_install_script << cmEscape(bmi_install.Destination) + << "\"\n" + " TYPE FILE\n"; + if (bmi_install.Optional) { + *bmi_install_script << " OPTIONAL\n"; + } + if (!bmi_install.MessageLevel.empty()) { + *bmi_install_script << " " << bmi_install.MessageLevel << "\n"; + } + if (!bmi_install.Permissions.empty()) { + *bmi_install_script << " PERMISSIONS" << bmi_install.Permissions + << "\n"; + } + *bmi_install_script << " FILES \"" << *m << "\")\n"; + if (bmi_dest_is_abs) { + *bmi_install_script + << " list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES\n" + " \"" + << cmEscape(cmSystemTools::GetFilenameName(*m)) + << "\")\n" + " if (CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION)\n" + " message(WARNING\n" + " \"ABSOLUTE path INSTALL DESTINATION : " + "${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n" + " endif ()\n" + " if (CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION)\n" + " message(FATAL_ERROR\n" + " \"ABSOLUTE path INSTALL DESTINATION forbidden (by " + "caller): ${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n" + " endif ()\n"; + } + *bmi_install_script << "endif ()\n"; + } + } + } + + // Add trailing parenthesis for the `set_property` call. + for (auto const& exp : exports) { + *exp.first << ")\n"; + } + + // Check that public sources only require public modules. + for (auto const& pub_reqs : public_source_requires) { + for (auto const& req : pub_reqs.second) { + if (private_modules.count(req)) { + cmSystemTools::Error(cmStrCat( + "Public C++ module source `", pub_reqs.first, "` requires the `", + req, "` C++ module which is provided by a private source")); + result = false; + } + } + } + + return result; +} |