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)
{
// 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

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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;