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:
parent
22be36f8d5
commit
7902bc06aa
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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<cmTarget*> 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<cmTarget*> const* Exports;
|
||||
cmExportCommand* ExportCommand;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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<cmTarget*> targets;
|
||||
for(std::vector<std::string>::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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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<std::string> 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<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.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<std::string> const& libs,
|
||||
ImportPropertyMap& properties);
|
||||
|
||||
/** Each subclass knows how to generate its kind of export file. */
|
||||
virtual bool GenerateMainFile(std::ostream& os) = 0;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -311,6 +311,28 @@ void cmTarget::DefineProperties(cmake *cm)
|
|||
"located on disk for the configuration <CONFIG>. "
|
||||
"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
|
||||
("MAP_IMPORTED_CONFIG_<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<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*
|
||||
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<void>(r);
|
||||
assert(r.empty());
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
cmTargetLinkInterfaceMap::~cmTargetLinkInterfaceMap()
|
||||
{
|
||||
for(derived::iterator i = this->begin(); i != this->end(); ++i)
|
||||
{
|
||||
delete i->second;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,20 @@ struct 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
|
||||
* \brief Represent a library or executable target loaded from a makefile.
|
||||
*
|
||||
|
@ -209,6 +223,12 @@ public:
|
|||
std::vector<std::string> 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;
|
||||
|
|
Loading…
Reference in New Issue