diff --git a/Source/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx index 6940b7c52..168464129 100644 --- a/Source/cmComputeLinkDepends.cxx +++ b/Source/cmComputeLinkDepends.cxx @@ -263,17 +263,22 @@ void cmComputeLinkDepends::FollowLinkEntry(BFSEntry const& qe) if(entry.Target) { // Follow the target dependencies. - if(entry.Target->GetType() != cmTarget::EXECUTABLE) + if(entry.Target->IsImported()) { - if(entry.Target->IsImported()) - { - this->AddImportedLinkEntries(depender_index, entry.Target); - } - else - { - this->AddTargetLinkEntries(depender_index, - entry.Target->GetOriginalLinkLibraries()); - } + // Imported targets provide their own link information. + this->AddImportedLinkEntries(depender_index, entry.Target); + } + else if(cmTargetLinkInterface const* interface = + entry.Target->GetLinkInterface(this->Config)) + { + // This target provides its own link interface information. + this->AddLinkEntries(depender_index, *interface); + } + else if(entry.Target->GetType() != cmTarget::EXECUTABLE) + { + // Use the target's link implementation as the interface. + this->AddTargetLinkEntries(depender_index, + entry.Target->GetOriginalLinkLibraries()); } } else diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx index f0dca0acb..618cd19de 100644 --- a/Source/cmExportBuildFileGenerator.cxx +++ b/Source/cmExportBuildFileGenerator.cxx @@ -16,6 +16,14 @@ =========================================================================*/ #include "cmExportBuildFileGenerator.h" +#include "cmExportCommand.h" + +//---------------------------------------------------------------------------- +cmExportBuildFileGenerator::cmExportBuildFileGenerator() +{ + this->ExportCommand = 0; +} + //---------------------------------------------------------------------------- bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) { @@ -116,9 +124,19 @@ void cmExportBuildFileGenerator ::ComplainAboutMissingTarget(cmTarget* target, const char* dep) { + if(!this->ExportCommand || !this->ExportCommand->ErrorMessage.empty()) + { + return; + } + cmOStringStream e; - e << "WARNING: EXPORT(...) includes target " << target->GetName() - << " which links to target \"" << dep - << "\" that is not in the export set."; - cmSystemTools::Message(e.str().c_str()); + e << "called with target \"" << target->GetName() + << "\" which links to target \"" << dep + << "\" that is not in the export list.\n" + << "If the link dependency is not part of the public interface " + << "consider setting the LINK_INTERFACE_LIBRARIES property on \"" + << target->GetName() << "\". Otherwise add it to the export list. " + << "If the link dependency is not easy to reference in this call, " + << "consider using the APPEND option with multiple separate calls."; + this->ExportCommand->ErrorMessage = e.str(); } diff --git a/Source/cmExportBuildFileGenerator.h b/Source/cmExportBuildFileGenerator.h index 53423f382..394e95aed 100644 --- a/Source/cmExportBuildFileGenerator.h +++ b/Source/cmExportBuildFileGenerator.h @@ -19,6 +19,8 @@ #include "cmExportFileGenerator.h" +class cmExportCommand; + /** \class cmExportBuildFileGenerator * \brief Generate a file exporting targets from a build tree. * @@ -31,12 +33,17 @@ class cmExportBuildFileGenerator: public cmExportFileGenerator { public: + cmExportBuildFileGenerator(); + /** Set the list of targets to export. */ void SetExports(std::vector const* exports) { this->Exports = exports; } /** Set whether to append generated code to the output file. */ void SetAppendMode(bool append) { this->AppendMode = append; } + + /** Set the command instance through which errors should be reported. */ + void SetCommand(cmExportCommand* cmd) { this->ExportCommand = cmd; } protected: // Implement virtual methods from the superclass. virtual bool GenerateMainFile(std::ostream& os); @@ -52,6 +59,7 @@ protected: ImportPropertyMap& properties); std::vector const* Exports; + cmExportCommand* ExportCommand; }; #endif diff --git a/Source/cmExportCommand.cxx b/Source/cmExportCommand.cxx index cfc339c33..ba47637b8 100644 --- a/Source/cmExportCommand.cxx +++ b/Source/cmExportCommand.cxx @@ -100,12 +100,6 @@ bool cmExportCommand fname += this->Filename.GetString(); } - // If no targets are to be exported we are done. - if(this->Targets.GetVector().empty()) - { - return true; - } - // Collect the targets to be exported. std::vector targets; for(std::vector::const_iterator @@ -149,6 +143,7 @@ bool cmExportCommand ebfg.SetNamespace(this->Namespace.GetCString()); ebfg.SetAppendMode(this->Append.IsEnabled()); ebfg.SetExports(&targets); + ebfg.SetCommand(this); // Compute the set of configurations exported. if(const char* types = @@ -180,5 +175,12 @@ bool cmExportCommand return false; } + // Report generated error message if any. + if(!this->ErrorMessage.empty()) + { + this->SetError(this->ErrorMessage.c_str()); + return false; + } + return true; } diff --git a/Source/cmExportCommand.h b/Source/cmExportCommand.h index d0e88c47a..22a93f3a1 100644 --- a/Source/cmExportCommand.h +++ b/Source/cmExportCommand.h @@ -19,6 +19,8 @@ #include "cmCommand.h" +class cmExportBuildFileGenerator; + /** \class cmExportLibraryDependenciesCommand * \brief Add a test to the lists of tests to run. * @@ -93,6 +95,9 @@ private: cmCAEnabler Append; cmCAString Namespace; cmCAString Filename; + + friend class cmExportBuildFileGenerator; + std::string ErrorMessage; }; diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 6cf30c954..c48aff840 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -135,9 +135,18 @@ cmExportFileGenerator } // Add the transitive link dependencies for this configuration. - if(target->GetType() == cmTarget::STATIC_LIBRARY || - target->GetType() == cmTarget::SHARED_LIBRARY) + if(cmTargetLinkInterface const* interface = + target->GetLinkInterface(config)) { + // This target provides a link interface, so use it. + this->SetImportLinkProperties(config, suffix, target, + *interface, properties); + } + else if(target->GetType() == cmTarget::STATIC_LIBRARY || + target->GetType() == cmTarget::SHARED_LIBRARY) + { + // The default link interface for static and shared libraries is + // their link implementation library list. this->SetImportLinkProperties(config, suffix, target, properties); } } @@ -148,9 +157,6 @@ cmExportFileGenerator ::SetImportLinkProperties(const char* config, std::string const& suffix, cmTarget* target, ImportPropertyMap& properties) { - // Get the makefile in which to lookup target information. - cmMakefile* mf = target->GetMakefile(); - // Compute which library configuration to link. cmTarget::LinkLibraryType linkType = cmTarget::OPTIMIZED; if(config && cmSystemTools::UpperCase(config) == "DEBUG") @@ -158,10 +164,10 @@ cmExportFileGenerator linkType = cmTarget::DEBUG; } - // Construct the property value. + // Construct the list of libs linked for this configuration. + std::vector actual_libs; cmTarget::LinkLibraryVectorType const& libs = target->GetOriginalLinkLibraries(); - std::string link_libs; const char* sep = ""; for(cmTarget::LinkLibraryVectorType::const_iterator li = libs.begin(); li != libs.end(); ++li) @@ -174,33 +180,66 @@ cmExportFileGenerator continue; } - // Separate this from the previous entry. - link_libs += sep; - sep = ";"; + // Store this entry. + actual_libs.push_back(li->first); + } + // Store the entries in the property. + this->SetImportLinkProperties(config, suffix, target, + actual_libs, properties); +} + +//---------------------------------------------------------------------------- +void +cmExportFileGenerator +::SetImportLinkProperties(const char* config, + std::string const& suffix, + cmTarget* target, + std::vector const& libs, + ImportPropertyMap& properties) +{ + // Get the makefile in which to lookup target information. + cmMakefile* mf = target->GetMakefile(); + + // Construct the property value. + std::string link_libs; + const char* sep = ""; + for(std::vector::const_iterator li = libs.begin(); + li != libs.end(); ++li) + { // Append this entry. - if(cmTarget* tgt = mf->FindTargetToUse(li->first.c_str())) + if(cmTarget* tgt = mf->FindTargetToUse(li->c_str())) { - // This is a target. Make sure it is included in the export. - if(this->ExportedTargets.find(tgt) != this->ExportedTargets.end()) + // This is a target. + if(tgt->IsImported()) + { + // The target is imported (and therefore is not in the + // export). Append the raw name. + link_libs += *li; + } + else if(this->ExportedTargets.find(tgt) != this->ExportedTargets.end()) { // The target is in the export. Append it with the export // namespace. link_libs += this->Namespace; - link_libs += li->first; + link_libs += *li; } else { - // The target is not in the export. This is probably - // user-error. Warn but add it anyway. - this->ComplainAboutMissingTarget(target, li->first.c_str()); - link_libs += li->first; + // The target is not in the export. + if(!this->AppendMode) + { + // We are not appending, so all exported targets should be + // known here. This is probably user-error. + this->ComplainAboutMissingTarget(target, li->c_str()); + } + link_libs += *li; } } else { // Append the raw name. - link_libs += li->first; + link_libs += *li; } } diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h index 27d998b3b..ac9cb758c 100644 --- a/Source/cmExportFileGenerator.h +++ b/Source/cmExportFileGenerator.h @@ -70,6 +70,11 @@ protected: void SetImportLinkProperties(const char* config, std::string const& suffix, cmTarget* target, ImportPropertyMap& properties); + void SetImportLinkProperties(const char* config, + std::string const& suffix, + cmTarget* target, + std::vector const& libs, + ImportPropertyMap& properties); /** Each subclass knows how to generate its kind of export file. */ virtual bool GenerateMainFile(std::ostream& os) = 0; diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx index 82af4e1f6..14bb81682 100644 --- a/Source/cmExportInstallFileGenerator.cxx +++ b/Source/cmExportInstallFileGenerator.cxx @@ -267,9 +267,13 @@ cmExportInstallFileGenerator ::ComplainAboutMissingTarget(cmTarget* target, const char* dep) { cmOStringStream e; - e << "WARNING: INSTALL(EXPORT \"" << this->Name << "\" ...) " - << "includes target " << target->GetName() - << " which links to target \"" << dep - << "\" that is not in the export set."; - cmSystemTools::Message(e.str().c_str()); + e << "INSTALL(EXPORT \"" << this->Name << "\" ...) " + << "includes target \"" << target->GetName() + << "\" which links to target \"" << dep + << "\" that is not in the export set. " + << "If the link dependency is not part of the public interface " + << "consider setting the LINK_INTERFACE_LIBRARIES property on " + << "target \"" << target->GetName() << "\". " + << "Otherwise add it to the export set."; + cmSystemTools::Error(e.str().c_str()); } diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 706a45fa0..6575140a6 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -311,6 +311,28 @@ void cmTarget::DefineProperties(cmake *cm) "located on disk for the configuration . " "The property is defined only for library and executable targets."); + cm->DefineProperty + ("LINK_INTERFACE_LIBRARIES", cmProperty::TARGET, + "List public interface libraries for a shared library or executable.", + "By default linking to a shared library target transitively " + "links to targets with which the library itself was linked. " + "For an executable with exports (see the ENABLE_EXPORTS property) " + "no default transitive link dependencies are used. " + "This property replaces the default transitive link dependencies with " + "an explict list. " + "When the target is linked into another target the libraries " + "listed (and recursively their link interface libraries) will be " + "provided to the other target also. " + "If the list is empty then no transitive link dependencies will be " + "incorporated when this target is linked into another target even if " + "the default set is non-empty."); + + cm->DefineProperty + ("LINK_INTERFACE_LIBRARIES_", cmProperty::TARGET, + "Per-configuration list of public interface libraries for a target.", + "This is the configuration-specific version of " + "LINK_INTERFACE_LIBRARIES."); + cm->DefineProperty ("MAP_IMPORTED_CONFIG_", cmProperty::TARGET, "Map from project configuration to IMPORTED target's configuration.", @@ -3040,6 +3062,80 @@ cmTarget::GetImportedLinkLibraries(const char* config) } } +//---------------------------------------------------------------------------- +cmTargetLinkInterface const* cmTarget::GetLinkInterface(const char* config) +{ + // Link interfaces are supported only for non-imported shared + // libraries and executables that export symbols. Imported targets + // provide their own link information. + if(this->IsImported() || + (this->GetType() != cmTarget::SHARED_LIBRARY && + !this->IsExecutableWithExports())) + { + return 0; + } + + // Lookup any existing link interface for this configuration. + std::map::iterator + i = this->LinkInterface.find(config?config:""); + if(i == this->LinkInterface.end()) + { + // Compute the link interface for this configuration. + cmTargetLinkInterface* interface = this->ComputeLinkInterface(config); + + // Store the information for this configuration. + std::map::value_type + entry(config?config:"", interface); + i = this->LinkInterface.insert(entry).first; + } + + return i->second; +} + +//---------------------------------------------------------------------------- +cmTargetLinkInterface* cmTarget::ComputeLinkInterface(const char* config) +{ + // Construct the property name suffix for this configuration. + std::string suffix = "_"; + if(config && *config) + { + suffix += cmSystemTools::UpperCase(config); + } + else + { + suffix += "NOCONFIG"; + } + + // Lookup the link interface libraries. + const char* libs = 0; + { + // Lookup the per-configuration property. + std::string propName = "LINK_INTERFACE_LIBRARIES"; + propName += suffix; + libs = this->GetProperty(propName.c_str()); + + // If not set, try the generic property. + if(!libs) + { + libs = this->GetProperty("LINK_INTERFACE_LIBRARIES"); + } + } + + // If still not set, there is no link interface. + if(!libs) + { + return 0; + } + + // Return the interface libraries even if the list is empty. + if(cmTargetLinkInterface* interface = new cmTargetLinkInterface) + { + cmSystemTools::ExpandListArgument(libs, *interface); + return interface; + } + return 0; +} + //---------------------------------------------------------------------------- cmComputeLinkInformation* cmTarget::GetLinkInformation(const char* config) @@ -3088,3 +3184,26 @@ cmTargetLinkInformationMap::~cmTargetLinkInformationMap() delete i->second; } } + +//---------------------------------------------------------------------------- +cmTargetLinkInterfaceMap +::cmTargetLinkInterfaceMap(cmTargetLinkInterfaceMap const& r): derived() +{ + // Ideally cmTarget instances should never be copied. However until + // we can make a sweep to remove that, this copy constructor avoids + // allowing the resources (LinkInterface) from getting copied. In + // the worst case this will lead to extra cmTargetLinkInterface + // instances. We also enforce in debug mode that the map be emptied + // when copied. + static_cast(r); + assert(r.empty()); +} + +//---------------------------------------------------------------------------- +cmTargetLinkInterfaceMap::~cmTargetLinkInterfaceMap() +{ + for(derived::iterator i = this->begin(); i != this->end(); ++i) + { + delete i->second; + } +} diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 37ab2039f..bbcdafc72 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -35,6 +35,20 @@ struct cmTargetLinkInformationMap: ~cmTargetLinkInformationMap(); }; +struct cmTargetLinkInterface: public std::vector +{ + typedef std::vector derived; +}; + +struct cmTargetLinkInterfaceMap: + public std::map +{ + typedef std::map derived; + cmTargetLinkInterfaceMap() {} + cmTargetLinkInterfaceMap(cmTargetLinkInterfaceMap const& r); + ~cmTargetLinkInterfaceMap(); +}; + /** \class cmTarget * \brief Represent a library or executable target loaded from a makefile. * @@ -209,6 +223,12 @@ public: std::vector const* GetImportedLinkLibraries(const char* config); + /** Get the library interface dependencies. This is the set of + libraries from which something that links to this target may + also receive symbols. Returns 0 if the user has not specified + such dependencies or for static libraries. */ + cmTargetLinkInterface const* GetLinkInterface(const char* config); + /** Get the directory in which this target will be built. If the configuration name is given then the generator will add its subdirectory for that configuration. Otherwise just the canonical @@ -476,6 +496,10 @@ private: cmTargetLinkInformationMap LinkInformation; + // Link interface. + cmTargetLinkInterface* ComputeLinkInterface(const char* config); + cmTargetLinkInterfaceMap LinkInterface; + // The cmMakefile instance that owns this target. This should // always be set. cmMakefile* Makefile;