ENH: Implemented link-interface specification feature.

- Shared libs and executables with exports may now have
    explicit transitive link dependencies specified
  - Created LINK_INTERFACE_LIBRARIES and related properties
  - Exported targets get the interface libraries as their
    IMPORTED_LINK_LIBRARIES property.
  - The export() and install(EXPORT) commands now give
    an error when a linked target is not included since
    the user can change the interface libraries instead
    of adding the target.
This commit is contained in:
Brad King 2008-01-30 17:25:52 -05:00
parent 22be36f8d5
commit 7902bc06aa
10 changed files with 273 additions and 44 deletions

View File

@ -263,17 +263,22 @@ void cmComputeLinkDepends::FollowLinkEntry(BFSEntry const& qe)
if(entry.Target) if(entry.Target)
{ {
// Follow the target dependencies. // Follow the target dependencies.
if(entry.Target->GetType() != cmTarget::EXECUTABLE) if(entry.Target->IsImported())
{ {
if(entry.Target->IsImported()) // Imported targets provide their own link information.
{ this->AddImportedLinkEntries(depender_index, entry.Target);
this->AddImportedLinkEntries(depender_index, entry.Target); }
} else if(cmTargetLinkInterface const* interface =
else entry.Target->GetLinkInterface(this->Config))
{ {
this->AddTargetLinkEntries(depender_index, // This target provides its own link interface information.
entry.Target->GetOriginalLinkLibraries()); 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 else

View File

@ -16,6 +16,14 @@
=========================================================================*/ =========================================================================*/
#include "cmExportBuildFileGenerator.h" #include "cmExportBuildFileGenerator.h"
#include "cmExportCommand.h"
//----------------------------------------------------------------------------
cmExportBuildFileGenerator::cmExportBuildFileGenerator()
{
this->ExportCommand = 0;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os) bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
{ {
@ -116,9 +124,19 @@ void
cmExportBuildFileGenerator cmExportBuildFileGenerator
::ComplainAboutMissingTarget(cmTarget* target, const char* dep) ::ComplainAboutMissingTarget(cmTarget* target, const char* dep)
{ {
if(!this->ExportCommand || !this->ExportCommand->ErrorMessage.empty())
{
return;
}
cmOStringStream e; cmOStringStream e;
e << "WARNING: EXPORT(...) includes target " << target->GetName() e << "called with target \"" << target->GetName()
<< " which links to target \"" << dep << "\" which links to target \"" << dep
<< "\" that is not in the export set."; << "\" that is not in the export list.\n"
cmSystemTools::Message(e.str().c_str()); << "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();
} }

View File

@ -19,6 +19,8 @@
#include "cmExportFileGenerator.h" #include "cmExportFileGenerator.h"
class cmExportCommand;
/** \class cmExportBuildFileGenerator /** \class cmExportBuildFileGenerator
* \brief Generate a file exporting targets from a build tree. * \brief Generate a file exporting targets from a build tree.
* *
@ -31,12 +33,17 @@
class cmExportBuildFileGenerator: public cmExportFileGenerator class cmExportBuildFileGenerator: public cmExportFileGenerator
{ {
public: public:
cmExportBuildFileGenerator();
/** Set the list of targets to export. */ /** Set the list of targets to export. */
void SetExports(std::vector<cmTarget*> const* exports) void SetExports(std::vector<cmTarget*> const* exports)
{ this->Exports = exports; } { this->Exports = exports; }
/** Set whether to append generated code to the output file. */ /** Set whether to append generated code to the output file. */
void SetAppendMode(bool append) { this->AppendMode = append; } 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: protected:
// Implement virtual methods from the superclass. // Implement virtual methods from the superclass.
virtual bool GenerateMainFile(std::ostream& os); virtual bool GenerateMainFile(std::ostream& os);
@ -52,6 +59,7 @@ protected:
ImportPropertyMap& properties); ImportPropertyMap& properties);
std::vector<cmTarget*> const* Exports; std::vector<cmTarget*> const* Exports;
cmExportCommand* ExportCommand;
}; };
#endif #endif

View File

@ -100,12 +100,6 @@ bool cmExportCommand
fname += this->Filename.GetString(); 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. // Collect the targets to be exported.
std::vector<cmTarget*> targets; std::vector<cmTarget*> targets;
for(std::vector<std::string>::const_iterator for(std::vector<std::string>::const_iterator
@ -149,6 +143,7 @@ bool cmExportCommand
ebfg.SetNamespace(this->Namespace.GetCString()); ebfg.SetNamespace(this->Namespace.GetCString());
ebfg.SetAppendMode(this->Append.IsEnabled()); ebfg.SetAppendMode(this->Append.IsEnabled());
ebfg.SetExports(&targets); ebfg.SetExports(&targets);
ebfg.SetCommand(this);
// Compute the set of configurations exported. // Compute the set of configurations exported.
if(const char* types = if(const char* types =
@ -180,5 +175,12 @@ bool cmExportCommand
return false; return false;
} }
// Report generated error message if any.
if(!this->ErrorMessage.empty())
{
this->SetError(this->ErrorMessage.c_str());
return false;
}
return true; return true;
} }

View File

@ -19,6 +19,8 @@
#include "cmCommand.h" #include "cmCommand.h"
class cmExportBuildFileGenerator;
/** \class cmExportLibraryDependenciesCommand /** \class cmExportLibraryDependenciesCommand
* \brief Add a test to the lists of tests to run. * \brief Add a test to the lists of tests to run.
* *
@ -93,6 +95,9 @@ private:
cmCAEnabler Append; cmCAEnabler Append;
cmCAString Namespace; cmCAString Namespace;
cmCAString Filename; cmCAString Filename;
friend class cmExportBuildFileGenerator;
std::string ErrorMessage;
}; };

View File

@ -135,9 +135,18 @@ cmExportFileGenerator
} }
// Add the transitive link dependencies for this configuration. // Add the transitive link dependencies for this configuration.
if(target->GetType() == cmTarget::STATIC_LIBRARY || if(cmTargetLinkInterface const* interface =
target->GetType() == cmTarget::SHARED_LIBRARY) 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); this->SetImportLinkProperties(config, suffix, target, properties);
} }
} }
@ -148,9 +157,6 @@ cmExportFileGenerator
::SetImportLinkProperties(const char* config, std::string const& suffix, ::SetImportLinkProperties(const char* config, std::string const& suffix,
cmTarget* target, ImportPropertyMap& properties) cmTarget* target, ImportPropertyMap& properties)
{ {
// Get the makefile in which to lookup target information.
cmMakefile* mf = target->GetMakefile();
// Compute which library configuration to link. // Compute which library configuration to link.
cmTarget::LinkLibraryType linkType = cmTarget::OPTIMIZED; cmTarget::LinkLibraryType linkType = cmTarget::OPTIMIZED;
if(config && cmSystemTools::UpperCase(config) == "DEBUG") if(config && cmSystemTools::UpperCase(config) == "DEBUG")
@ -158,10 +164,10 @@ cmExportFileGenerator
linkType = cmTarget::DEBUG; linkType = cmTarget::DEBUG;
} }
// Construct the property value. // Construct the list of libs linked for this configuration.
std::vector<std::string> actual_libs;
cmTarget::LinkLibraryVectorType const& libs = cmTarget::LinkLibraryVectorType const& libs =
target->GetOriginalLinkLibraries(); target->GetOriginalLinkLibraries();
std::string link_libs;
const char* sep = ""; const char* sep = "";
for(cmTarget::LinkLibraryVectorType::const_iterator li = libs.begin(); for(cmTarget::LinkLibraryVectorType::const_iterator li = libs.begin();
li != libs.end(); ++li) li != libs.end(); ++li)
@ -174,33 +180,66 @@ cmExportFileGenerator
continue; continue;
} }
// Separate this from the previous entry. // Store this entry.
link_libs += sep; actual_libs.push_back(li->first);
sep = ";"; }
// 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<std::string> 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<std::string>::const_iterator li = libs.begin();
li != libs.end(); ++li)
{
// Append this entry. // 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. // This is a target.
if(this->ExportedTargets.find(tgt) != this->ExportedTargets.end()) 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 // The target is in the export. Append it with the export
// namespace. // namespace.
link_libs += this->Namespace; link_libs += this->Namespace;
link_libs += li->first; link_libs += *li;
} }
else else
{ {
// The target is not in the export. This is probably // The target is not in the export.
// user-error. Warn but add it anyway. if(!this->AppendMode)
this->ComplainAboutMissingTarget(target, li->first.c_str()); {
link_libs += li->first; // 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 else
{ {
// Append the raw name. // Append the raw name.
link_libs += li->first; link_libs += *li;
} }
} }

View File

@ -70,6 +70,11 @@ protected:
void SetImportLinkProperties(const char* config, void SetImportLinkProperties(const char* config,
std::string const& suffix, cmTarget* target, std::string const& suffix, cmTarget* target,
ImportPropertyMap& properties); ImportPropertyMap& properties);
void SetImportLinkProperties(const char* config,
std::string const& suffix,
cmTarget* target,
std::vector<std::string> const& libs,
ImportPropertyMap& properties);
/** Each subclass knows how to generate its kind of export file. */ /** Each subclass knows how to generate its kind of export file. */
virtual bool GenerateMainFile(std::ostream& os) = 0; virtual bool GenerateMainFile(std::ostream& os) = 0;

View File

@ -267,9 +267,13 @@ cmExportInstallFileGenerator
::ComplainAboutMissingTarget(cmTarget* target, const char* dep) ::ComplainAboutMissingTarget(cmTarget* target, const char* dep)
{ {
cmOStringStream e; cmOStringStream e;
e << "WARNING: INSTALL(EXPORT \"" << this->Name << "\" ...) " e << "INSTALL(EXPORT \"" << this->Name << "\" ...) "
<< "includes target " << target->GetName() << "includes target \"" << target->GetName()
<< " which links to target \"" << dep << "\" which links to target \"" << dep
<< "\" that is not in the export set."; << "\" that is not in the export set. "
cmSystemTools::Message(e.str().c_str()); << "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());
} }

View File

@ -311,6 +311,28 @@ void cmTarget::DefineProperties(cmake *cm)
"located on disk for the configuration <CONFIG>. " "located on disk for the configuration <CONFIG>. "
"The property is defined only for library and executable targets."); "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_<CONFIG>", cmProperty::TARGET,
"Per-configuration list of public interface libraries for a target.",
"This is the configuration-specific version of "
"LINK_INTERFACE_LIBRARIES.");
cm->DefineProperty cm->DefineProperty
("MAP_IMPORTED_CONFIG_<CONFIG>", cmProperty::TARGET, ("MAP_IMPORTED_CONFIG_<CONFIG>", cmProperty::TARGET,
"Map from project configuration to IMPORTED target's configuration.", "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<cmStdString, cmTargetLinkInterface*>::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<cmStdString, cmTargetLinkInterface*>::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* cmComputeLinkInformation*
cmTarget::GetLinkInformation(const char* config) cmTarget::GetLinkInformation(const char* config)
@ -3088,3 +3184,26 @@ cmTargetLinkInformationMap::~cmTargetLinkInformationMap()
delete i->second; 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<void>(r);
assert(r.empty());
}
//----------------------------------------------------------------------------
cmTargetLinkInterfaceMap::~cmTargetLinkInterfaceMap()
{
for(derived::iterator i = this->begin(); i != this->end(); ++i)
{
delete i->second;
}
}

View File

@ -35,6 +35,20 @@ struct cmTargetLinkInformationMap:
~cmTargetLinkInformationMap(); ~cmTargetLinkInformationMap();
}; };
struct cmTargetLinkInterface: public std::vector<std::string>
{
typedef std::vector<std::string> derived;
};
struct cmTargetLinkInterfaceMap:
public std::map<cmStdString, cmTargetLinkInterface*>
{
typedef std::map<cmStdString, cmTargetLinkInterface*> derived;
cmTargetLinkInterfaceMap() {}
cmTargetLinkInterfaceMap(cmTargetLinkInterfaceMap const& r);
~cmTargetLinkInterfaceMap();
};
/** \class cmTarget /** \class cmTarget
* \brief Represent a library or executable target loaded from a makefile. * \brief Represent a library or executable target loaded from a makefile.
* *
@ -209,6 +223,12 @@ public:
std::vector<std::string> const* std::vector<std::string> const*
GetImportedLinkLibraries(const char* config); 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 /** Get the directory in which this target will be built. If the
configuration name is given then the generator will add its configuration name is given then the generator will add its
subdirectory for that configuration. Otherwise just the canonical subdirectory for that configuration. Otherwise just the canonical
@ -476,6 +496,10 @@ private:
cmTargetLinkInformationMap LinkInformation; cmTargetLinkInformationMap LinkInformation;
// Link interface.
cmTargetLinkInterface* ComputeLinkInterface(const char* config);
cmTargetLinkInterfaceMap LinkInterface;
// The cmMakefile instance that owns this target. This should // The cmMakefile instance that owns this target. This should
// always be set. // always be set.
cmMakefile* Makefile; cmMakefile* Makefile;