Commit 7902bc06 authored by Brad King's avatar Brad King
Browse files

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.
parent 22be36f8
......@@ -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;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment