/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmExportInstallFileGenerator.h" #include #include #include #include #include #include #include #include "cmExportSet.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmInstallTargetGenerator.h" #include "cmList.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetExport.h" #include "cmValue.h" #include "cmake.h" cmExportInstallFileGenerator::cmExportInstallFileGenerator( cmInstallExportGenerator* iegen) : IEGen(iegen) { } void cmExportInstallFileGenerator::ReplaceInstallPrefix( std::string& input) const { cmGeneratorExpression::ReplaceInstallPrefix(input, this->GetInstallPrefix()); } void cmExportInstallFileGenerator::PopulateImportProperties( std::string const& config, std::string const& suffix, cmTargetExport const* targetExport, ImportPropertyMap& properties, std::set& importedLocations) { this->SetImportLocationProperty(config, suffix, targetExport->ArchiveGenerator, properties, importedLocations); this->SetImportLocationProperty(config, suffix, targetExport->LibraryGenerator, properties, importedLocations); this->SetImportLocationProperty(config, suffix, targetExport->RuntimeGenerator, properties, importedLocations); this->SetImportLocationProperty(config, suffix, targetExport->ObjectsGenerator, properties, importedLocations); this->SetImportLocationProperty(config, suffix, targetExport->FrameworkGenerator, properties, importedLocations); this->SetImportLocationProperty(config, suffix, targetExport->BundleGenerator, properties, importedLocations); // If any file location was set for the target add it to the // import file. if (!properties.empty()) { // Get the rest of the target details. cmGeneratorTarget const* const gtgt = targetExport->Target; this->SetImportDetailProperties(config, suffix, gtgt, properties); // TODO: PUBLIC_HEADER_LOCATION // This should wait until the build feature propagation stuff is done. // Then this can be a propagated include directory. // this->GenerateImportProperty(config, te->HeaderGenerator, properties); } } std::string cmExportInstallFileGenerator::GetImportXcFrameworkLocation( std::string const& config, cmTargetExport const* targetExport) const { std::string importedXcFrameworkLocation = targetExport->XcFrameworkLocation; if (!importedXcFrameworkLocation.empty()) { importedXcFrameworkLocation = cmGeneratorExpression::Preprocess( importedXcFrameworkLocation, cmGeneratorExpression::PreprocessContext::InstallInterface, this->GetImportPrefixWithSlash()); importedXcFrameworkLocation = cmGeneratorExpression::Evaluate( importedXcFrameworkLocation, targetExport->Target->GetLocalGenerator(), config, targetExport->Target, nullptr, targetExport->Target); if (!importedXcFrameworkLocation.empty() && !cmSystemTools::FileIsFullPath(importedXcFrameworkLocation) && !cmHasPrefix(importedXcFrameworkLocation, this->GetImportPrefixWithSlash())) { return cmStrCat(this->GetImportPrefixWithSlash(), importedXcFrameworkLocation); } } return importedXcFrameworkLocation; } bool cmExportInstallFileGenerator::GenerateImportFileConfig( std::string const& config) { // Skip configurations not enabled for this export. if (!this->IEGen->InstallsForConfig(config)) { return true; } // Construct the name of the file to generate. std::string fileName = cmStrCat(this->FileDir, '/', this->FileBase, this->GetConfigFileNameSeparator()); if (!config.empty()) { fileName += cmSystemTools::LowerCase(config); } else { fileName += "noconfig"; } fileName += this->FileExt; // Open the output file to generate it. cmGeneratedFileStream exportFileStream(fileName, true); if (!exportFileStream) { std::string se = cmSystemTools::GetLastSystemError(); std::ostringstream e; e << "cannot write to file \"" << fileName << "\": " << se; cmSystemTools::Error(e.str()); return false; } exportFileStream.SetCopyIfDifferent(true); std::ostream& os = exportFileStream; // Generate the per-config target information. this->GenerateImportConfig(os, config); // Record this per-config import file. this->ConfigImportFiles[config] = fileName; return true; } void cmExportInstallFileGenerator::SetImportLocationProperty( std::string const& config, std::string const& suffix, cmInstallTargetGenerator* itgen, ImportPropertyMap& properties, std::set& importedLocations) { // Skip rules that do not match this configuration. if (!(itgen && itgen->InstallsForConfig(config))) { return; } // Get the target to be installed. cmGeneratorTarget* target = itgen->GetTarget(); // Construct the installed location of the target. std::string dest = itgen->GetDestination(config); std::string value; if (!cmSystemTools::FileIsFullPath(dest)) { // The target is installed relative to the installation prefix. value = std::string{ this->GetImportPrefixWithSlash() }; } value += dest; value += "/"; if (itgen->IsImportLibrary()) { // Construct the property name. std::string prop = cmStrCat("IMPORTED_IMPLIB", suffix); // Append the installed file name. value += cmInstallTargetGenerator::GetInstallFilename( target, config, cmInstallTargetGenerator::NameImplibReal); // Store the property. properties[prop] = value; importedLocations.insert(prop); } else if (itgen->GetTarget()->GetType() == cmStateEnums::OBJECT_LIBRARY) { // Construct the property name. std::string prop = cmStrCat("IMPORTED_OBJECTS", suffix); // Compute all the object files inside this target and setup // IMPORTED_OBJECTS as a list of object files std::vector objects; itgen->GetInstallObjectNames(config, objects); for (std::string& obj : objects) { obj = cmStrCat(value, obj); } // Store the property. properties[prop] = cmList::to_string(objects); importedLocations.insert(prop); } else { if (target->IsFrameworkOnApple() && target->HasImportLibrary(config)) { // store as well IMPLIB value auto importProp = cmStrCat("IMPORTED_IMPLIB", suffix); auto importValue = cmStrCat(value, cmInstallTargetGenerator::GetInstallFilename( target, config, cmInstallTargetGenerator::NameImplibReal)); // Store the property. properties[importProp] = importValue; importedLocations.insert(importProp); } // Construct the property name. std::string prop = cmStrCat("IMPORTED_LOCATION", suffix); // Append the installed file name. if (target->IsAppBundleOnApple()) { value += cmInstallTargetGenerator::GetInstallFilename(target, config); value += ".app/"; if (!target->Makefile->PlatformIsAppleEmbedded()) { value += "Contents/MacOS/"; } value += cmInstallTargetGenerator::GetInstallFilename(target, config); } else { value += cmInstallTargetGenerator::GetInstallFilename( target, config, cmInstallTargetGenerator::NameReal); } // Store the property. properties[prop] = value; importedLocations.insert(prop); } } cmStateEnums::TargetType cmExportInstallFileGenerator::GetExportTargetType( cmTargetExport const* targetExport) const { cmStateEnums::TargetType targetType = targetExport->Target->GetType(); // An OBJECT library installed with no OBJECTS DESTINATION // is transformed to an INTERFACE library. if (targetType == cmStateEnums::OBJECT_LIBRARY && !targetExport->ObjectsGenerator) { targetType = cmStateEnums::INTERFACE_LIBRARY; } return targetType; } std::string const& cmExportInstallFileGenerator::GetExportName() const { return this->GetExportSet()->GetName(); } void cmExportInstallFileGenerator::HandleMissingTarget( std::string& link_libs, cmGeneratorTarget const* depender, cmGeneratorTarget* dependee) { auto const& exportInfo = this->FindExportInfo(dependee); if (exportInfo.Namespaces.size() == 1 && exportInfo.Sets.size() == 1) { std::string missingTarget = *exportInfo.Namespaces.begin(); missingTarget += dependee->GetExportName(); link_libs += missingTarget; this->MissingTargets.emplace_back(std::move(missingTarget)); } else { // All exported targets should be known here and should be unique. // This is probably user-error. this->ComplainAboutMissingTarget(depender, dependee, exportInfo); } } cmExportFileGenerator::ExportInfo cmExportInstallFileGenerator::FindExportInfo( cmGeneratorTarget const* target) const { std::vector exportFiles; std::set exportSets; std::set namespaces; auto const& name = target->GetName(); auto& allExportSets = target->GetLocalGenerator()->GetGlobalGenerator()->GetExportSets(); for (auto const& exp : allExportSets) { auto const& exportSet = exp.second; auto const& targets = exportSet.GetTargetExports(); if (std::any_of(targets.begin(), targets.end(), [&name](std::unique_ptr const& te) { return te->TargetName == name; })) { std::vector const* installs = exportSet.GetInstallations(); if (!installs->empty()) { exportSets.insert(exp.first); for (cmInstallExportGenerator const* install : *installs) { exportFiles.push_back(install->GetDestinationFile()); namespaces.insert(install->GetNamespace()); } } } } return { exportFiles, exportSets, namespaces }; } void cmExportInstallFileGenerator::ComplainAboutMissingTarget( cmGeneratorTarget const* depender, cmGeneratorTarget const* dependee, ExportInfo const& exportInfo) const { std::ostringstream e; e << "install(" << this->IEGen->InstallSubcommand() << " \"" << this->GetExportName() << "\" ...) " << "includes target \"" << depender->GetName() << "\" which requires target \"" << dependee->GetName() << "\" "; if (exportInfo.Sets.empty()) { e << "that is not in any export set."; } else { if (exportInfo.Sets.size() == 1) { e << "that is not in this export set, but in another export set which " "is " "exported multiple times with different namespaces: "; } else { e << "that is not in this export set, but in multiple other export " "sets: "; } e << cmJoin(exportInfo.Files, ", ") << ".\n" << "An exported target cannot depend upon another target which is " "exported in more than one export set or with more than one " "namespace. " "Consider consolidating the exports of the \"" << dependee->GetName() << "\" target to a single export."; } this->ReportError(e.str()); } void cmExportInstallFileGenerator::ComplainAboutDuplicateTarget( std::string const& targetName) const { std::ostringstream e; e << "install(" << this->IEGen->InstallSubcommand() << " \"" << this->GetExportName() << "\" ...) " << "includes target \"" << targetName << "\" more than once in the export set."; this->ReportError(e.str()); } void cmExportInstallFileGenerator::ReportError( std::string const& errorMessage) const { this->IEGen->GetLocalGenerator()->GetCMakeInstance()->IssueMessage( MessageType::FATAL_ERROR, errorMessage, this->IEGen->GetLocalGenerator()->GetMakefile()->GetBacktrace()); } std::string cmExportInstallFileGenerator::InstallNameDir( cmGeneratorTarget const* target, std::string const& config) { std::string install_name_dir; cmMakefile* mf = target->Target->GetMakefile(); if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { auto const& prefix = this->GetInstallPrefix(); install_name_dir = target->GetInstallNameDirForInstallTree(config, prefix); } return install_name_dir; } std::string cmExportInstallFileGenerator::GetCxxModuleFile() const { return this->GetCxxModuleFile(this->GetExportSet()->GetName()); } bool cmExportInstallFileGenerator::CollectExports( std::function const& visitor) { auto pred = [&](std::unique_ptr const& te) -> bool { if (te->NamelinkOnly) { return true; } if (this->ExportedTargets.insert(te->Target).second) { visitor(te.get()); return true; } this->ComplainAboutDuplicateTarget(te->Target->GetName()); return false; }; auto const& targets = this->GetExportSet()->GetTargetExports(); return std::all_of(targets.begin(), targets.end(), pred); } bool cmExportInstallFileGenerator::PopulateInterfaceProperties( cmTargetExport const* targetExport, ImportPropertyMap& properties) { cmGeneratorTarget const* const gt = targetExport->Target; std::string includesDestinationDirs; this->PopulateInterfaceProperty("INTERFACE_SYSTEM_INCLUDE_DIRECTORIES", gt, cmGeneratorExpression::InstallInterface, properties); this->PopulateIncludeDirectoriesInterface( gt, cmGeneratorExpression::InstallInterface, properties, *targetExport, includesDestinationDirs); this->PopulateLinkDirectoriesInterface( gt, cmGeneratorExpression::InstallInterface, properties); this->PopulateLinkDependsInterface( gt, cmGeneratorExpression::InstallInterface, properties); this->PopulateSourcesInterface(gt, cmGeneratorExpression::InstallInterface, properties); return this->PopulateInterfaceProperties( gt, includesDestinationDirs, cmGeneratorExpression::InstallInterface, properties); } namespace { bool isSubDirectory(std::string const& a, std::string const& b) { return (cmSystemTools::ComparePath(a, b) || cmSystemTools::IsSubDirectory(a, b)); } } bool cmExportInstallFileGenerator::CheckInterfaceDirs( std::string const& prepro, cmGeneratorTarget const* target, std::string const& prop) const { std::string const& installDir = target->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX"); std::string const& topSourceDir = target->GetLocalGenerator()->GetSourceDirectory(); std::string const& topBinaryDir = target->GetLocalGenerator()->GetBinaryDirectory(); std::vector parts; cmGeneratorExpression::Split(prepro, parts); bool const inSourceBuild = topSourceDir == topBinaryDir; bool hadFatalError = false; for (std::string const& li : parts) { size_t genexPos = cmGeneratorExpression::Find(li); if (genexPos == 0) { continue; } if (cmHasPrefix(li, this->GetImportPrefixWithSlash())) { continue; } std::ostringstream e; if (genexPos != std::string::npos) { hadFatalError = true; } if (!cmSystemTools::FileIsFullPath(li)) { /* clang-format off */ e << "Target \"" << target->GetName() << "\" " << prop << " property contains relative path:\n" " \"" << li << "\""; /* clang-format on */ target->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, e.str()); } bool inBinary = isSubDirectory(li, topBinaryDir); bool inSource = isSubDirectory(li, topSourceDir); if (isSubDirectory(li, installDir)) { // The include directory is inside the install tree. If the // install tree is inside the source tree or build tree then do not // fall through to the checks below that the include directory is not // also inside the source tree or build tree. if ((!inBinary || isSubDirectory(installDir, topBinaryDir)) && (!inSource || isSubDirectory(installDir, topSourceDir))) { continue; } } if (inBinary) { /* clang-format off */ e << "Target \"" << target->GetName() << "\" " << prop << " property contains path:\n" " \"" << li << "\"\nwhich is prefixed in the build directory."; /* clang-format on */ target->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, e.str()); } if (!inSourceBuild) { if (inSource) { e << "Target \"" << target->GetName() << "\" " << prop << " property contains path:\n" " \"" << li << "\"\nwhich is prefixed in the source directory."; target->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, e.str()); } } } return !hadFatalError; } void cmExportInstallFileGenerator::PopulateSourcesInterface( cmGeneratorTarget const* gt, cmGeneratorExpression::PreprocessContext preprocessRule, ImportPropertyMap& properties) { assert(preprocessRule == cmGeneratorExpression::InstallInterface); char const* const propName = "INTERFACE_SOURCES"; cmValue input = gt->GetProperty(propName); if (!input) { return; } if (input->empty()) { properties[propName].clear(); return; } std::string prepro = cmGeneratorExpression::Preprocess( *input, preprocessRule, this->GetImportPrefixWithSlash()); if (!prepro.empty()) { this->ResolveTargetsInGeneratorExpressions(prepro, gt); if (!this->CheckInterfaceDirs(prepro, gt, propName)) { return; } properties[propName] = prepro; } } void cmExportInstallFileGenerator::PopulateIncludeDirectoriesInterface( cmGeneratorTarget const* target, cmGeneratorExpression::PreprocessContext preprocessRule, ImportPropertyMap& properties, cmTargetExport const& te, std::string& includesDestinationDirs) { assert(preprocessRule == cmGeneratorExpression::InstallInterface); includesDestinationDirs.clear(); char const* const propName = "INTERFACE_INCLUDE_DIRECTORIES"; cmValue input = target->GetProperty(propName); cmGeneratorExpression ge(*target->Makefile->GetCMakeInstance()); std::string dirs = cmGeneratorExpression::Preprocess( cmList::to_string(target->Target->GetInstallIncludeDirectoriesEntries(te)), preprocessRule, this->GetImportPrefixWithSlash()); this->ReplaceInstallPrefix(dirs); std::unique_ptr cge = ge.Parse(dirs); std::string exportDirs = cge->Evaluate(target->GetLocalGenerator(), "", target); if (cge->GetHadContextSensitiveCondition()) { cmLocalGenerator* lg = target->GetLocalGenerator(); std::ostringstream e; e << "Target \"" << target->GetName() << "\" is installed with " "INCLUDES DESTINATION set to a context sensitive path. Paths which " "depend on the configuration, policy values or the link interface " "are " "not supported. Consider using target_include_directories instead."; lg->IssueMessage(MessageType::FATAL_ERROR, e.str()); return; } if (!input && exportDirs.empty()) { return; } if ((input && input->empty()) && exportDirs.empty()) { // Set to empty properties[propName].clear(); return; } this->AddImportPrefix(exportDirs); includesDestinationDirs = exportDirs; std::string includes = (input ? *input : ""); char const* const sep = input ? ";" : ""; includes += sep + exportDirs; std::string prepro = cmGeneratorExpression::Preprocess( includes, preprocessRule, this->GetImportPrefixWithSlash()); if (!prepro.empty()) { this->ResolveTargetsInGeneratorExpressions(prepro, target); if (!this->CheckInterfaceDirs(prepro, target, propName)) { return; } properties[propName] = prepro; } } void cmExportInstallFileGenerator::PopulateLinkDependsInterface( cmGeneratorTarget const* gt, cmGeneratorExpression::PreprocessContext preprocessRule, ImportPropertyMap& properties) { assert(preprocessRule == cmGeneratorExpression::InstallInterface); char const* const propName = "INTERFACE_LINK_DEPENDS"; cmValue input = gt->GetProperty(propName); if (!input) { return; } if (input->empty()) { properties[propName].clear(); return; } std::string prepro = cmGeneratorExpression::Preprocess( *input, preprocessRule, this->GetImportPrefixWithSlash()); if (!prepro.empty()) { this->ResolveTargetsInGeneratorExpressions(prepro, gt); if (!this->CheckInterfaceDirs(prepro, gt, propName)) { return; } properties[propName] = prepro; } } void cmExportInstallFileGenerator::PopulateLinkDirectoriesInterface( cmGeneratorTarget const* gt, cmGeneratorExpression::PreprocessContext preprocessRule, ImportPropertyMap& properties) { assert(preprocessRule == cmGeneratorExpression::InstallInterface); char const* const propName = "INTERFACE_LINK_DIRECTORIES"; cmValue input = gt->GetProperty(propName); if (!input) { return; } if (input->empty()) { properties[propName].clear(); return; } std::string prepro = cmGeneratorExpression::Preprocess( *input, preprocessRule, this->GetImportPrefixWithSlash()); if (!prepro.empty()) { this->ResolveTargetsInGeneratorExpressions(prepro, gt); if (!this->CheckInterfaceDirs(prepro, gt, propName)) { return; } properties[propName] = prepro; } }