| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmExportCMakeConfigGenerator.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <sstream> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include <vector> | |
| #include <cm/optional> | |
| #include <cm/string_view> | |
| #include <cmext/string_view> | |
| #include "cmExportSet.h" | |
| #include "cmFileSetMetadata.h" | |
| #include "cmFindPackageStack.h" | |
| #include "cmGeneratedFileStream.h" | |
| #include "cmGeneratorFileSet.h" | |
| #include "cmGeneratorFileSets.h" | |
| #include "cmGeneratorTarget.h" | |
| #include "cmLocalGenerator.h" | |
| #include "cmMessageType.h" | |
| #include "cmOutputConverter.h" | |
| #include "cmScriptGenerator.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTarget.h" | |
| #include "cmTargetTypes.h" | |
| #include "cmValue.h" | |
| #include "cmVersionMacros.h" | |
| struct cmLinkInterface; | |
| static std::string cmExportFileGeneratorEscape(std::string const& str) | |
| { | |
| // Escape a property value for writing into a .cmake file. | |
| std::string result = cmOutputConverter::EscapeForCMake(str); | |
| // Un-escape variable references generated by our own export code. | |
| cmSystemTools::ReplaceString(result, "\\${_IMPORT_PREFIX}", | |
| "${_IMPORT_PREFIX}"); | |
| cmSystemTools::ReplaceString(result, "\\${CMAKE_IMPORT_LIBRARY_SUFFIX}", | |
| "${CMAKE_IMPORT_LIBRARY_SUFFIX}"); | |
| return result; | |
| } | |
| cmExportCMakeConfigGenerator::cmExportCMakeConfigGenerator() = default; | |
| cm::string_view cmExportCMakeConfigGenerator::GetImportPrefixWithSlash() const | |
| { | |
| return "${_IMPORT_PREFIX}/"_s; | |
| } | |
| bool cmExportCMakeConfigGenerator::GenerateImportFile(std::ostream& os) | |
| { | |
| std::stringstream mainFileWithHeadersAndFootersBuffer; | |
| // Start with the import file header. | |
| this->GenerateImportHeaderCode(mainFileWithHeadersAndFootersBuffer); | |
| // Create all the imported targets. | |
| std::stringstream mainFileBuffer; | |
| bool result = this->GenerateMainFile(mainFileBuffer); | |
| // Export find_dependency() calls. Must be done after GenerateMainFile(), | |
| // because that's when target dependencies are gathered, which we need for | |
| // the find_dependency() calls. | |
| if (!this->AppendMode && this->GetExportSet() && | |
| this->ExportPackageDependencies) { | |
| this->SetRequiredCMakeVersion(3, 9, 0); | |
| this->GenerateFindDependencyCalls(mainFileWithHeadersAndFootersBuffer); | |
| } | |
| // Write cached import code. | |
| mainFileWithHeadersAndFootersBuffer << mainFileBuffer.rdbuf(); | |
| // End with the import file footer. | |
| this->GenerateImportFooterCode(mainFileWithHeadersAndFootersBuffer); | |
| this->GeneratePolicyFooterCode(mainFileWithHeadersAndFootersBuffer); | |
| // This has to be done last, after the minimum CMake version has been | |
| // determined. | |
| this->GeneratePolicyHeaderCode(os); | |
| os << mainFileWithHeadersAndFootersBuffer.rdbuf(); | |
| return result; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateInterfaceProperties( | |
| cmGeneratorTarget const* target, std::ostream& os, | |
| ImportPropertyMap const& properties) | |
| { | |
| if (!properties.empty()) { | |
| std::string targetName = | |
| cmStrCat(this->Namespace, target->GetExportName()); | |
| os << "set_target_properties(" << targetName << " PROPERTIES\n"; | |
| for (auto const& property : properties) { | |
| os << " " << property.first << ' ' | |
| << cmExportFileGeneratorEscape(property.second) << '\n'; | |
| } | |
| os << ")\n\n"; | |
| } | |
| } | |
| void cmExportCMakeConfigGenerator::SetImportLinkInterface( | |
| std::string const& config, std::string const& suffix, | |
| cmGeneratorExpression::PreprocessContext preprocessRule, | |
| cmGeneratorTarget const* target, ImportPropertyMap& properties) | |
| { | |
| // Add the transitive link dependencies for this configuration. | |
| cmLinkInterface const* iface = target->GetLinkInterface(config, target); | |
| if (!iface) { | |
| return; | |
| } | |
| cmValue propContent; | |
| if (cmValue prop_suffixed = | |
| target->GetProperty("LINK_INTERFACE_LIBRARIES" + suffix)) { | |
| propContent = prop_suffixed; | |
| } else if (cmValue prop = target->GetProperty("LINK_INTERFACE_LIBRARIES")) { | |
| propContent = prop; | |
| } else { | |
| return; | |
| } | |
| if (!this->ExportOld) { | |
| cmLocalGenerator* lg = target->GetLocalGenerator(); | |
| lg->IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat( | |
| "Target \"", target->GetName(), | |
| "\" has policy CMP0022 enabled, " | |
| "but also has old-style LINK_INTERFACE_LIBRARIES properties " | |
| "populated, but it was exported without the " | |
| "EXPORT_LINK_INTERFACE_LIBRARIES to export the old-style properties")); | |
| return; | |
| } | |
| if (propContent->empty()) { | |
| properties["IMPORTED_LINK_INTERFACE_LIBRARIES" + suffix].clear(); | |
| return; | |
| } | |
| std::string prepro = | |
| cmGeneratorExpression::Preprocess(*propContent, preprocessRule); | |
| if (!prepro.empty()) { | |
| this->ResolveTargetsInGeneratorExpressions(prepro, target, | |
| ReplaceFreeTargets); | |
| properties["IMPORTED_LINK_INTERFACE_LIBRARIES" + suffix] = prepro; | |
| } | |
| } | |
| void cmExportCMakeConfigGenerator::GeneratePolicyHeaderCode(std::ostream& os) | |
| { | |
| // Protect that file against use with older CMake versions. | |
| /* clang-format off */ | |
| os << "# Generated by CMake\n\n" | |
| "if(\"${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}\" LESS 2.8)\n" | |
| " message(FATAL_ERROR \"CMake >= " | |
| << this->RequiredCMakeVersionMajor << '.' | |
| << this->RequiredCMakeVersionMinor << '.' | |
| << this->RequiredCMakeVersionPatch << " required\")\n" | |
| "endif()\n" | |
| "if(CMAKE_VERSION VERSION_LESS \"" | |
| << this->RequiredCMakeVersionMajor << '.' | |
| << this->RequiredCMakeVersionMinor << '.' | |
| << this->RequiredCMakeVersionPatch << "\")\n" | |
| " message(FATAL_ERROR \"CMake >= " | |
| << this->RequiredCMakeVersionMajor << '.' | |
| << this->RequiredCMakeVersionMinor << '.' | |
| << this->RequiredCMakeVersionPatch << " required\")\n" | |
| "endif()\n"; | |
| /* clang-format on */ | |
| // Isolate the file policy level. | |
| // Support CMake versions as far back as the | |
| // RequiredCMakeVersion{Major,Minor,Patch}, but also support using NEW | |
| // policy settings for up to CMake 4.3 (this upper limit may be reviewed | |
| // and increased from time to time). This reduces the opportunity for CMake | |
| // warnings when an older export file is later used with newer CMake | |
| // versions. | |
| /* clang-format off */ | |
| os << "cmake_policy(PUSH)\n" | |
| "cmake_policy(VERSION " | |
| << this->RequiredCMakeVersionMajor << '.' | |
| << this->RequiredCMakeVersionMinor << '.' | |
| << this->RequiredCMakeVersionPatch; | |
| /* clang-format on */ | |
| if (CMake_VERSION_ENCODE( | |
| this->RequiredCMakeVersionMajor, this->RequiredCMakeVersionMinor, | |
| this->RequiredCMakeVersionPatch) <= CMake_VERSION_ENCODE(4, 3, 0)) { | |
| os << "...4.3"; | |
| } | |
| os << ")\n"; | |
| } | |
| void cmExportCMakeConfigGenerator::GeneratePolicyFooterCode(std::ostream& os) | |
| { | |
| os << "cmake_policy(POP)\n"; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateImportHeaderCode( | |
| std::ostream& os, std::string const& config) | |
| { | |
| os << "#----------------------------------------------------------------\n" | |
| "# Generated CMake target import file"; | |
| if (!config.empty()) { | |
| os << " for configuration \"" << config << "\".\n"; | |
| } else { | |
| os << ".\n"; | |
| } | |
| os << "#----------------------------------------------------------------\n" | |
| "\n"; | |
| this->GenerateImportVersionCode(os); | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateImportFooterCode(std::ostream& os) | |
| { | |
| os << "# Commands beyond this point should not need to know the version.\n" | |
| "set(CMAKE_IMPORT_FILE_VERSION)\n"; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateImportVersionCode(std::ostream& os) | |
| { | |
| // Store an import file format version. This will let us change the | |
| // format later while still allowing old import files to work. | |
| os << "# Commands may need to know the format version.\n" | |
| "set(CMAKE_IMPORT_FILE_VERSION 1)\n" | |
| "\n"; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateExpectedTargetsCode( | |
| std::ostream& os, std::string const& expectedTargets) | |
| { | |
| /* clang-format off */ | |
| os << "# Protect against multiple inclusion, which would fail when already " | |
| "imported targets are added once more.\n" | |
| "set(_cmake_targets_defined \"\")\n" | |
| "set(_cmake_targets_not_defined \"\")\n" | |
| "set(_cmake_expected_targets \"\")\n" | |
| "foreach(_cmake_expected_target IN ITEMS " << expectedTargets << ")\n" | |
| " list(APPEND _cmake_expected_targets \"${_cmake_expected_target}\")\n" | |
| " if(TARGET \"${_cmake_expected_target}\")\n" | |
| " list(APPEND _cmake_targets_defined \"${_cmake_expected_target}\")\n" | |
| " else()\n" | |
| " list(APPEND _cmake_targets_not_defined \"${_cmake_expected_target}\")\n" | |
| " endif()\n" | |
| "endforeach()\n" | |
| "unset(_cmake_expected_target)\n" | |
| "if(_cmake_targets_defined STREQUAL _cmake_expected_targets)\n" | |
| " unset(_cmake_targets_defined)\n" | |
| " unset(_cmake_targets_not_defined)\n" | |
| " unset(_cmake_expected_targets)\n" | |
| " unset(CMAKE_IMPORT_FILE_VERSION)\n" | |
| " cmake_policy(POP)\n" | |
| " return()\n" | |
| "endif()\n" | |
| "if(NOT _cmake_targets_defined STREQUAL \"\")\n" | |
| " string(REPLACE \";\" \", \" _cmake_targets_defined_text \"${_cmake_targets_defined}\")\n" | |
| " string(REPLACE \";\" \", \" _cmake_targets_not_defined_text \"${_cmake_targets_not_defined}\")\n" | |
| " message(FATAL_ERROR \"Some (but not all) targets in this export " | |
| "set were already defined.\\nTargets Defined: ${_cmake_targets_defined_text}\\n" | |
| "Targets not yet defined: ${_cmake_targets_not_defined_text}\\n\")\n" | |
| "endif()\n" | |
| "unset(_cmake_targets_defined)\n" | |
| "unset(_cmake_targets_not_defined)\n" | |
| "unset(_cmake_expected_targets)\n" | |
| "\n\n"; | |
| /* clang-format on */ | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateImportTargetCode( | |
| std::ostream& os, cmGeneratorTarget const* target, cm::TargetType targetType) | |
| { | |
| // Construct the imported target name. | |
| std::string targetName = this->Namespace; | |
| targetName += target->GetExportName(); | |
| // Create the imported target. | |
| os << "# Create imported target " << targetName << "\n"; | |
| switch (targetType) { | |
| case cm::TargetType::EXECUTABLE: | |
| os << "add_executable(" << targetName << " IMPORTED)\n"; | |
| break; | |
| case cm::TargetType::STATIC_LIBRARY: | |
| os << "add_library(" << targetName << " STATIC IMPORTED)\n"; | |
| break; | |
| case cm::TargetType::SHARED_LIBRARY: | |
| os << "add_library(" << targetName << " SHARED IMPORTED)\n"; | |
| break; | |
| case cm::TargetType::MODULE_LIBRARY: | |
| os << "add_library(" << targetName << " MODULE IMPORTED)\n"; | |
| break; | |
| case cm::TargetType::UNKNOWN_LIBRARY: | |
| os << "add_library(" << targetName << " UNKNOWN IMPORTED)\n"; | |
| break; | |
| case cm::TargetType::OBJECT_LIBRARY: | |
| os << "add_library(" << targetName << " OBJECT IMPORTED)\n"; | |
| break; | |
| case cm::TargetType::INTERFACE_LIBRARY: | |
| if (target->IsSymbolic()) { | |
| os << "if(CMAKE_VERSION VERSION_GREATER_EQUAL \"4.2.0\")\n" | |
| " add_library(" | |
| << targetName << " INTERFACE SYMBOLIC IMPORTED)\n" | |
| << "elseif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.2.0)\n" | |
| << " add_library(" << targetName << " INTERFACE IMPORTED)\n" | |
| << " set_target_properties(" << targetName | |
| << " PROPERTIES SYMBOLIC TRUE)\n" | |
| << "endif()\n"; | |
| } else { | |
| os << "add_library(" << targetName << " INTERFACE IMPORTED)\n"; | |
| } | |
| break; | |
| default: // should never happen | |
| break; | |
| } | |
| // Mark the imported executable if it has exports. | |
| if (target->IsExecutableWithExports() || | |
| (target->IsSharedLibraryWithExports() && target->HasImportLibrary(""))) { | |
| os << "set_property(TARGET " << targetName | |
| << " PROPERTY ENABLE_EXPORTS 1)\n"; | |
| } | |
| // Mark the imported library if it is a framework. | |
| if (target->IsFrameworkOnApple()) { | |
| os << "set_property(TARGET " << targetName << " PROPERTY FRAMEWORK 1)\n"; | |
| } | |
| // Mark the imported executable if it is an application bundle. | |
| if (target->IsAppBundleOnApple()) { | |
| os << "set_property(TARGET " << targetName | |
| << " PROPERTY MACOSX_BUNDLE 1)\n"; | |
| } | |
| if (target->IsCFBundleOnApple()) { | |
| os << "set_property(TARGET " << targetName << " PROPERTY BUNDLE 1)\n"; | |
| } | |
| // Mark the imported library if it is an AIX shared library archive. | |
| if (target->IsArchivedAIXSharedLibrary()) { | |
| os << "set_property(TARGET " << targetName | |
| << " PROPERTY AIX_SHARED_LIBRARY_ARCHIVE 1)\n"; | |
| } | |
| // generate DEPRECATION | |
| if (target->IsDeprecated()) { | |
| os << "set_property(TARGET " << targetName << " PROPERTY DEPRECATION " | |
| << cmExportFileGeneratorEscape(target->GetDeprecation()) << ")\n"; | |
| } | |
| if (target->GetPropertyAsBool("IMPORTED_NO_SYSTEM")) { | |
| os << "set_property(TARGET " << targetName | |
| << " PROPERTY IMPORTED_NO_SYSTEM 1)\n"; | |
| } | |
| if (target->GetPropertyAsBool("EXPORT_NO_SYSTEM")) { | |
| os << "set_property(TARGET " << targetName << " PROPERTY SYSTEM 0)\n"; | |
| } | |
| os << '\n'; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateImportPropertyCode( | |
| std::ostream& os, std::string const& config, std::string const& suffix, | |
| cmGeneratorTarget const* target, ImportPropertyMap const& properties, | |
| std::string const& importedXcFrameworkLocation) | |
| { | |
| // Construct the imported target name. | |
| std::string targetName = this->Namespace; | |
| targetName += target->GetExportName(); | |
| // Set the import properties. | |
| os << "# Import target \"" << targetName << "\" for configuration \"" | |
| << config | |
| << "\"\n" | |
| "set_property(TARGET " | |
| << targetName << " APPEND PROPERTY IMPORTED_CONFIGURATIONS "; | |
| if (!config.empty()) { | |
| os << cmSystemTools::UpperCase(config); | |
| } else { | |
| os << "NOCONFIG"; | |
| } | |
| os << ")\n" | |
| "set_target_properties(" | |
| << targetName << " PROPERTIES\n"; | |
| std::string importedLocationProp = cmStrCat("IMPORTED_LOCATION", suffix); | |
| for (auto const& property : properties) { | |
| if (importedXcFrameworkLocation.empty() || | |
| property.first != importedLocationProp) { | |
| os << " " << property.first << ' ' | |
| << cmExportFileGeneratorEscape(property.second) << '\n'; | |
| } | |
| } | |
| os << " )\n"; | |
| if (!importedXcFrameworkLocation.empty()) { | |
| auto importedLocationIt = properties.find(importedLocationProp); | |
| if (importedLocationIt != properties.end()) { | |
| os << "if(NOT CMAKE_VERSION VERSION_LESS \"3.28\" AND IS_DIRECTORY " | |
| << cmExportFileGeneratorEscape(importedXcFrameworkLocation) | |
| << ")\n" | |
| " set_property(TARGET " | |
| << targetName << " PROPERTY " << importedLocationProp << ' ' | |
| << cmExportFileGeneratorEscape(importedXcFrameworkLocation) | |
| << ")\nelse()\n set_property(TARGET " << targetName << " PROPERTY " | |
| << importedLocationProp << ' ' | |
| << cmExportFileGeneratorEscape(importedLocationIt->second) | |
| << ")\nendif()\n"; | |
| } | |
| } | |
| os << '\n'; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateFindDependencyCalls( | |
| std::ostream& os) | |
| { | |
| os << "include(CMakeFindDependencyMacro)\n"; | |
| std::map<std::string, cmExportSet::PackageDependency> packageDependencies; | |
| auto* exportSet = this->GetExportSet(); | |
| if (exportSet) { | |
| packageDependencies = exportSet->GetPackageDependencies(); | |
| } | |
| for (cmGeneratorTarget const* gt : this->ExternalTargets) { | |
| std::string findPackageName; | |
| auto exportFindPackageName = gt->GetProperty("EXPORT_FIND_PACKAGE_NAME"); | |
| cmFindPackageStack pkgStack = gt->Target->GetFindPackageStack(); | |
| if (!exportFindPackageName.IsEmpty()) { | |
| findPackageName = *exportFindPackageName; | |
| } else { | |
| if (!pkgStack.Empty()) { | |
| cmFindPackageCall const& fpc = pkgStack.Top(); | |
| findPackageName = fpc.Name; | |
| } | |
| } | |
| if (!findPackageName.empty()) { | |
| auto& dep = packageDependencies[findPackageName]; | |
| if (!pkgStack.Empty()) { | |
| dep.FindPackageIndex = pkgStack.Top().Index; | |
| } | |
| if (dep.Enabled == cmExportSet::PackageDependencyExportEnabled::Auto) { | |
| dep.Enabled = cmExportSet::PackageDependencyExportEnabled::On; | |
| } | |
| } | |
| } | |
| std::vector<std::pair<std::string, cmExportSet::PackageDependency>> | |
| packageDependenciesSorted(packageDependencies.begin(), | |
| packageDependencies.end()); | |
| std::sort( | |
| packageDependenciesSorted.begin(), packageDependenciesSorted.end(), | |
| [](std::pair<std::string, cmExportSet::PackageDependency> const& lhs, | |
| std::pair<std::string, cmExportSet::PackageDependency> const& rhs) | |
| -> bool { | |
| if (lhs.second.SpecifiedIndex) { | |
| if (rhs.second.SpecifiedIndex) { | |
| return lhs.second.SpecifiedIndex < rhs.second.SpecifiedIndex; | |
| } | |
| assert(rhs.second.FindPackageIndex); | |
| return true; | |
| } | |
| assert(lhs.second.FindPackageIndex); | |
| if (rhs.second.SpecifiedIndex) { | |
| return false; | |
| } | |
| assert(rhs.second.FindPackageIndex); | |
| return lhs.second.FindPackageIndex < rhs.second.FindPackageIndex; | |
| }); | |
| // Unwinding is only valid in a find_package() context | |
| os << "if(DEFINED CMAKE_FIND_PACKAGE_NAME)\n" | |
| << " set(_cmake_unwind_arg UNWIND_INCLUDE)\n" | |
| << "endif()\n\n"; | |
| for (auto const& it : packageDependenciesSorted) { | |
| if (it.second.Enabled == cmExportSet::PackageDependencyExportEnabled::On) { | |
| os << "__find_dependency_no_return(" << it.first; | |
| for (auto const& arg : it.second.ExtraArguments) { | |
| os << ' ' << cmScriptGenerator::Quote(arg); | |
| } | |
| os << " ${_cmake_unwind_arg})\n"; | |
| os << "if(NOT " << it.first << "_FOUND)\n" | |
| << " unset(_cmake_unwind_arg)\n" | |
| << " cmake_policy(POP)\n" | |
| << " return()\n" | |
| << "endif()\n\n"; | |
| } | |
| } | |
| os << "unset(_cmake_unwind_arg)\n\n\n"; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateMissingTargetsCheckCode( | |
| std::ostream& os) | |
| { | |
| if (this->MissingTargets.empty()) { | |
| os << "# This file does not depend on other imported targets which have\n" | |
| "# been exported from the same project but in a separate " | |
| "export set.\n\n"; | |
| return; | |
| } | |
| os << "# Make sure the targets which have been exported in some other\n" | |
| "# export set exist.\n" | |
| "unset(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)\n" | |
| "foreach(_target "; | |
| std::set<std::string> emitted; | |
| for (std::string const& missingTarget : this->MissingTargets) { | |
| if (emitted.insert(missingTarget).second) { | |
| os << '"' << missingTarget << "\" "; | |
| } | |
| } | |
| os << ")\n" | |
| " if(NOT TARGET \"${_target}\" )\n" | |
| " set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets \"" | |
| "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets} ${_target}\")" | |
| "\n" | |
| " endif()\n" | |
| "endforeach()\n" | |
| "\n" | |
| "if(DEFINED ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)\n" | |
| " if(CMAKE_FIND_PACKAGE_NAME)\n" | |
| " set( ${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)\n" | |
| " set( ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE " | |
| "\"The following imported targets are " | |
| "referenced, but are missing: " | |
| "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets}\")\n" | |
| " else()\n" | |
| " message(FATAL_ERROR \"The following imported targets are " | |
| "referenced, but are missing: " | |
| "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets}\")\n" | |
| " endif()\n" | |
| "endif()\n" | |
| "unset(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)\n" | |
| "\n"; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateImportedFileCheckLoop( | |
| std::ostream& os) | |
| { | |
| // Add code which verifies at cmake time that the file which is being | |
| // imported actually exists on disk. This should in theory always be theory | |
| // case, but still when packages are split into normal and development | |
| // packages this might get broken (e.g. the Config.cmake could be part of | |
| // the non-development package, something similar happened to me without | |
| // on SUSE with a mysql pkg-config file, which claimed everything is fine, | |
| // but the development package was not installed.). | |
| os << "# Loop over all imported files and verify that they actually exist\n" | |
| "foreach(_cmake_target IN LISTS _cmake_import_check_targets)\n" | |
| " if(CMAKE_VERSION VERSION_LESS \"3.28\"\n" | |
| " OR NOT DEFINED " | |
| "_cmake_import_check_xcframework_for_${_cmake_target}\n" | |
| " OR NOT IS_DIRECTORY " | |
| "\"${_cmake_import_check_xcframework_for_${_cmake_target}}\")\n" | |
| " foreach(_cmake_file IN LISTS " | |
| "\"_cmake_import_check_files_for_${_cmake_target}\")\n" | |
| " if(NOT EXISTS \"${_cmake_file}\")\n" | |
| " message(FATAL_ERROR \"The imported target " | |
| "\\\"${_cmake_target}\\\" references the file\n" | |
| " \\\"${_cmake_file}\\\"\n" | |
| "but this file does not exist. Possible reasons include:\n" | |
| "* The file was deleted, renamed, or moved to another location.\n" | |
| "* An install or uninstall procedure did not complete successfully.\n" | |
| "* The installation package was faulty and contained\n" | |
| " \\\"${CMAKE_CURRENT_LIST_FILE}\\\"\n" | |
| "but not all the files it references.\n" | |
| "\")\n" | |
| " endif()\n" | |
| " endforeach()\n" | |
| " endif()\n" | |
| " unset(_cmake_file)\n" | |
| " unset(\"_cmake_import_check_files_for_${_cmake_target}\")\n" | |
| "endforeach()\n" | |
| "unset(_cmake_target)\n" | |
| "unset(_cmake_import_check_targets)\n" | |
| "\n"; | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateImportedFileChecksCode( | |
| std::ostream& os, cmGeneratorTarget const* target, | |
| ImportPropertyMap const& properties, | |
| std::set<std::string> const& importedLocations, | |
| std::string const& importedXcFrameworkLocation) | |
| { | |
| // Construct the imported target name. | |
| std::string targetName = cmStrCat(this->Namespace, target->GetExportName()); | |
| os << "list(APPEND _cmake_import_check_targets " << targetName << " )\n"; | |
| if (!importedXcFrameworkLocation.empty()) { | |
| os << "set(_cmake_import_check_xcframework_for_" << targetName << ' ' | |
| << cmExportFileGeneratorEscape(importedXcFrameworkLocation) << ")\n"; | |
| } | |
| os << "list(APPEND _cmake_import_check_files_for_" << targetName << ' '; | |
| for (std::string const& li : importedLocations) { | |
| auto pi = properties.find(li); | |
| if (pi != properties.end()) { | |
| os << cmExportFileGeneratorEscape(pi->second) << ' '; | |
| } | |
| } | |
| os << ")\n\n"; | |
| } | |
| namespace { | |
| struct FileSetInformation | |
| { | |
| cm::string_view Type; | |
| cm::string_view CMakeVersion; | |
| }; | |
| cm::optional<FileSetInformation> GetFileSetInformation(cm::string_view type) | |
| { | |
| static std::unordered_map<cm::string_view, FileSetInformation> | |
| fileSetsInformation{ | |
| { cm::FileSetMetadata::HEADERS, | |
| { cm::FileSetMetadata::HEADERS, "3.23.0"_s } }, | |
| { cm::FileSetMetadata::SOURCES, | |
| { cm::FileSetMetadata::SOURCES, "4.4.0"_s } }, | |
| { cm::FileSetMetadata::CXX_MODULES, | |
| { cm::FileSetMetadata::CXX_MODULES, "3.28.0"_s } }, | |
| }; | |
| auto fsInfo = fileSetsInformation.find(type); | |
| if (fsInfo != fileSetsInformation.end()) { | |
| return fsInfo->second; | |
| } | |
| return {}; | |
| } | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateTargetFileSets( | |
| cmGeneratorTarget* gte, std::ostream& os, | |
| ImportFileSetPropertyMap const& properties, cmTargetExport const* te) | |
| { | |
| cmGeneratorFileSets const* gfs = gte->GetGeneratorFileSets(); | |
| auto const& types = gfs->GetInterfaceFileSetTypes(); | |
| if (!types.empty()) { | |
| std::string targetName = cmStrCat(this->Namespace, gte->GetExportName()); | |
| for (auto const& type : types) { | |
| auto fsInfo = GetFileSetInformation(type); | |
| bool const requiresFileSetLanguage = fsInfo && | |
| std::any_of(gfs->GetInterfaceFileSets(type).begin(), | |
| gfs->GetInterfaceFileSets(type).end(), | |
| [](cmGeneratorFileSet const* fileSet) { | |
| return fileSet->GetName() == cm::FileSetMetadata::SYCL || | |
| fileSet->GetName() == | |
| cm::FileSetMetadata::SYCL_HEADERS || | |
| !fileSet->GetProperty("LANGUAGE").IsEmpty(); | |
| }); | |
| if (requiresFileSetLanguage) { | |
| fsInfo->CMakeVersion = "4.5.0"_s; | |
| } | |
| if (fsInfo) { | |
| os << "if(NOT CMAKE_VERSION VERSION_LESS \"" << fsInfo->CMakeVersion | |
| << "\")\n "; | |
| } | |
| os << "target_sources(" << targetName << '\n'; | |
| for (auto const* fileSet : gfs->GetInterfaceFileSets(type)) { | |
| os << " INTERFACE" | |
| << "\n FILE_SET " | |
| << cmScriptGenerator::Quote(fileSet->GetName()) << "\n TYPE " | |
| << cmScriptGenerator::Quote(type) << "\n BASE_DIRS " | |
| << this->GetFileSetDirectories(gte, fileSet, te) << "\n FILES " | |
| << this->GetFileSetFiles(gte, fileSet, te) << '\n'; | |
| } | |
| os << " )\n"; | |
| for (auto const* fileSet : gfs->GetInterfaceFileSets(type)) { | |
| auto fsProperties = properties.find(fileSet->GetName()); | |
| if (fsProperties != properties.end()) { | |
| for (auto const& property : fsProperties->second) { | |
| os << "\n set_property(FILE_SET " | |
| << cmScriptGenerator::Quote(fileSet->GetName()) << " TARGET " | |
| << targetName << "\n PROPERTY " << property.first << ' ' | |
| << cmExportFileGeneratorEscape(property.second) << "\n )"; | |
| } | |
| } | |
| } | |
| if (fsInfo) { | |
| if (type == cm::FileSetMetadata::HEADERS) { | |
| if (requiresFileSetLanguage) { | |
| os << "\nelse()\n message(FATAL_ERROR \"The target '" | |
| << targetName | |
| << "' cannot be imported because it relies on the 'LANGUAGE' " | |
| "property of a 'HEADERS' file set which is not supported " | |
| "by this CMake version.\")"; | |
| } else { | |
| os << "\nelse()\n set_property(TARGET " << targetName | |
| << "\n APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES"; | |
| for (auto const* fileSet : gfs->GetInterfaceFileSets(type)) { | |
| os << "\n " | |
| << this->GetFileSetDirectories(gte, fileSet, te); | |
| } | |
| os << "\n )"; | |
| } | |
| } else if (type == cm::FileSetMetadata::SOURCES) { | |
| os << "\nelse()\n message(FATAL_ERROR \"The target '" << targetName | |
| << "' cannot be imported because it relies on 'FILE_SET' of type " | |
| "'SOURCES' which is not supported by this CMake version.\")"; | |
| } else if (type == cm::FileSetMetadata::CXX_MODULES) { | |
| os << "\nelse()\n message(AUTHOR_WARNING \"The target '" | |
| << targetName | |
| << "' cannot be imported properly because it relies on C++ " | |
| "modules which are not supported by this CMake version.\")"; | |
| } | |
| os << "\nendif()\n\n"; | |
| } else { | |
| os << ")\n\n"; | |
| } | |
| } | |
| } | |
| } | |
| std::string cmExportCMakeConfigGenerator::GetCxxModuleFile( | |
| std::string const& name) const | |
| { | |
| auto const& cxxModuleDirname = this->GetCxxModulesDirectory(); | |
| if (cxxModuleDirname.empty()) { | |
| return {}; | |
| } | |
| return cmStrCat(cmSystemTools::GetFilenamePath(this->MainImportFile), '/', | |
| cxxModuleDirname, "/cxx-modules-", name, ".cmake"); | |
| } | |
| void cmExportCMakeConfigGenerator::GenerateCxxModuleInformation( | |
| std::string const& name, std::ostream& os) | |
| { | |
| auto const cxx_module_dirname = this->GetCxxModulesDirectory(); | |
| if (cxx_module_dirname.empty()) { | |
| return; | |
| } | |
| // Write the include. | |
| os << "# Include C++ module properties\n" | |
| "include(\"${CMAKE_CURRENT_LIST_DIR}/" | |
| << cxx_module_dirname << "/cxx-modules-" << name << ".cmake\")\n\n"; | |
| // Include all configuration-specific include files. | |
| cmGeneratedFileStream ap(this->GetCxxModuleFile(name), true); | |
| ap.SetCopyIfDifferent(true); | |
| this->GenerateCxxModuleConfigInformation(name, ap); | |
| } | |
| void cmExportCMakeConfigGenerator::SetRequiredCMakeVersion(unsigned int major, | |
| unsigned int minor, | |
| unsigned int patch) | |
| { | |
| if (CMake_VERSION_ENCODE(major, minor, patch) > | |
| CMake_VERSION_ENCODE(this->RequiredCMakeVersionMajor, | |
| this->RequiredCMakeVersionMinor, | |
| this->RequiredCMakeVersionPatch)) { | |
| this->RequiredCMakeVersionMajor = major; | |
| this->RequiredCMakeVersionMinor = minor; | |
| this->RequiredCMakeVersionPatch = patch; | |
| } | |
| } |