You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cmake/Source/cmExportInstallFileGenerato...

682 lines
23 KiB

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmExportInstallFileGenerator.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <memory>
#include <set>
#include <sstream>
#include <utility>
#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 "cmPolicies.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTargetExport.h"
#include "cmValue.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<std::string>& 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<std::string>& 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<std::string> 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);
auto const& exportFiles = exportInfo.first;
if (exportFiles.size() == 1) {
std::string missingTarget = exportInfo.second;
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, exportFiles);
}
}
cmExportFileGenerator::ExportInfo cmExportInstallFileGenerator::FindExportInfo(
cmGeneratorTarget const* target) const
{
std::vector<std::string> exportFiles;
std::string ns;
auto const& name = target->GetName();
auto& exportSets =
target->GetLocalGenerator()->GetGlobalGenerator()->GetExportSets();
for (auto const& exp : exportSets) {
auto const& exportSet = exp.second;
auto const& targets = exportSet.GetTargetExports();
if (std::any_of(targets.begin(), targets.end(),
[&name](std::unique_ptr<cmTargetExport> const& te) {
return te->TargetName == name;
})) {
std::vector<cmInstallExportGenerator const*> const* installs =
exportSet.GetInstallations();
for (cmInstallExportGenerator const* install : *installs) {
exportFiles.push_back(install->GetDestinationFile());
ns = install->GetNamespace();
}
}
}
return { exportFiles, exportFiles.size() == 1 ? ns : std::string{} };
}
void cmExportInstallFileGenerator::ComplainAboutMissingTarget(
cmGeneratorTarget const* depender, cmGeneratorTarget const* dependee,
std::vector<std::string> const& exportFiles) const
{
std::ostringstream e;
e << "install(" << this->IEGen->InstallSubcommand() << " \""
<< this->GetExportName() << "\" ...) "
<< "includes target \"" << depender->GetName()
<< "\" which requires target \"" << dependee->GetName() << "\" ";
if (exportFiles.empty()) {
e << "that is not in any export set.";
} else {
e << "that is not in this export set, but in multiple other export sets: "
<< cmJoin(exportFiles, ", ") << ".\n";
e << "An exported target cannot depend upon another target which is "
"exported multiple times. 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
{
cmSystemTools::Error(errorMessage);
}
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<void(cmTargetExport const*)> const& visitor)
{
auto pred = [&](std::unique_ptr<cmTargetExport> 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<std::string> 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;
}
MessageType messageType = MessageType::FATAL_ERROR;
std::ostringstream e;
if (genexPos != std::string::npos) {
if (prop == "INTERFACE_INCLUDE_DIRECTORIES") {
switch (target->GetPolicyStatusCMP0041()) {
case cmPolicies::WARN:
messageType = MessageType::WARNING;
e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0041) << "\n";
break;
case cmPolicies::OLD:
continue;
case cmPolicies::REQUIRED_IF_USED:
case cmPolicies::REQUIRED_ALWAYS:
case cmPolicies::NEW:
hadFatalError = true;
break; // Issue fatal message.
}
} else {
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, 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 not inside the source tree or build tree then
// fall through to the checks below that the include directory is not
// also inside the source tree or build tree.
bool shouldContinue =
(!inBinary || isSubDirectory(installDir, topBinaryDir)) &&
(!inSource || isSubDirectory(installDir, topSourceDir));
if (prop == "INTERFACE_INCLUDE_DIRECTORIES") {
if (!shouldContinue) {
switch (target->GetPolicyStatusCMP0052()) {
case cmPolicies::WARN: {
std::ostringstream s;
s << cmPolicies::GetPolicyWarning(cmPolicies::CMP0052) << "\n";
s << "Directory:\n \"" << li
<< "\"\nin "
"INTERFACE_INCLUDE_DIRECTORIES of target \""
<< target->GetName()
<< "\" is a subdirectory of the install "
"directory:\n \""
<< installDir
<< "\"\nhowever it is also "
"a subdirectory of the "
<< (inBinary ? "build" : "source") << " tree:\n \""
<< (inBinary ? topBinaryDir : topSourceDir) << "\"\n";
target->GetLocalGenerator()->IssueMessage(
MessageType::AUTHOR_WARNING, s.str());
CM_FALLTHROUGH;
}
case cmPolicies::OLD:
shouldContinue = true;
break;
case cmPolicies::REQUIRED_ALWAYS:
case cmPolicies::REQUIRED_IF_USED:
case cmPolicies::NEW:
break;
}
}
}
if (shouldContinue) {
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, 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, 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<cmCompiledGeneratorExpression> 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;
}
}