Commit 2cff26fa authored by Brad King's avatar Brad King
Browse files

ENH: Support linking to shared libs with dependent libs

  - Split IMPORTED_LINK_LIBRARIES into two parts:
      IMPORTED_LINK_INTERFACE_LIBRARIES
      IMPORTED_LINK_DEPENDENT_LIBRARIES
  - Add CMAKE_DEPENDENT_SHARED_LIBRARY_MODE to select behavior
  - Set mode to LINK for Darwin (fixes universal binary problem)
  - Update ExportImport test to account for changes
parent 52e75800
......@@ -103,6 +103,12 @@ IF(XCODE)
SET(CMAKE_INCLUDE_SYSTEM_FLAG_CXX)
ENDIF(XCODE)
# Need to list dependent shared libraries on link line. When building
# with -isysroot (for universal binaries), the linker always looks for
# dependent libraries under the sysroot. Listing them on the link
# line works around the problem.
SET(CMAKE_DEPENDENT_SHARED_LIBRARY_MODE "LINK")
SET(CMAKE_MacOSX_Content_COMPILE_OBJECT "\"${CMAKE_COMMAND}\" -E copy_if_different <SOURCE> <OBJECT>")
SET(CMAKE_C_CREATE_SHARED_LIBRARY_FORBIDDEN_FLAGS -w)
......
......@@ -171,6 +171,14 @@ cmComputeLinkDepends::Compute()
this->FollowLinkEntry(qe);
}
// Complete the search of shared library dependencies.
while(!this->SharedDepQueue.empty())
{
// Handle the next entry.
this->HandleSharedDependency(this->SharedDepQueue.front());
this->SharedDepQueue.pop();
}
// Infer dependencies of targets for which they were not known.
this->InferDependencies();
......@@ -197,6 +205,20 @@ cmComputeLinkDepends::Compute()
return this->FinalLinkEntries;
}
//----------------------------------------------------------------------------
std::map<cmStdString, int>::iterator
cmComputeLinkDepends::AllocateLinkEntry(std::string const& item)
{
std::map<cmStdString, int>::value_type
index_entry(item, static_cast<int>(this->EntryList.size()));
std::map<cmStdString, int>::iterator
lei = this->LinkEntryIndex.insert(index_entry).first;
this->EntryList.push_back(LinkEntry());
this->InferredDependSets.push_back(0);
this->EntryConstraintGraph.push_back(EntryConstraintSet());
return lei;
}
//----------------------------------------------------------------------------
int cmComputeLinkDepends::AddLinkEntry(std::string const& item)
{
......@@ -209,14 +231,7 @@ int cmComputeLinkDepends::AddLinkEntry(std::string const& item)
}
// Allocate a spot for the item entry.
{
std::map<cmStdString, int>::value_type
index_entry(item, static_cast<int>(this->EntryList.size()));
lei = this->LinkEntryIndex.insert(index_entry).first;
this->EntryList.push_back(LinkEntry());
this->InferredDependSets.push_back(0);
this->EntryConstraintGraph.push_back(EntryConstraintSet());
}
lei = this->AllocateLinkEntry(item);
// Initialize the item entry.
int index = lei->second;
......@@ -263,18 +278,17 @@ void cmComputeLinkDepends::FollowLinkEntry(BFSEntry const& qe)
if(entry.Target)
{
// Follow the target dependencies.
if(entry.Target->IsImported())
{
// Imported targets provide their own link information.
this->AddImportedLinkEntries(depender_index, entry.Target);
}
else if(cmTargetLinkInterface const* interface =
entry.Target->GetLinkInterface(this->Config))
if(cmTargetLinkInterface const* interface =
entry.Target->GetLinkInterface(this->Config))
{
// This target provides its own link interface information.
this->AddLinkEntries(depender_index, *interface);
this->AddLinkEntries(depender_index, interface->Libraries);
// Handle dependent shared libraries.
this->QueueSharedDependencies(depender_index, interface->SharedDeps);
}
else if(entry.Target->GetType() != cmTarget::EXECUTABLE)
else if(!entry.Target->IsImported() &&
entry.Target->GetType() != cmTarget::EXECUTABLE)
{
// Use the target's link implementation as the interface.
this->AddTargetLinkEntries(depender_index,
......@@ -289,13 +303,60 @@ void cmComputeLinkDepends::FollowLinkEntry(BFSEntry const& qe)
}
//----------------------------------------------------------------------------
void cmComputeLinkDepends::AddImportedLinkEntries(int depender_index,
cmTarget* target)
void
cmComputeLinkDepends
::QueueSharedDependencies(int depender_index,
std::vector<std::string> const& deps)
{
for(std::vector<std::string>::const_iterator li = deps.begin();
li != deps.end(); ++li)
{
SharedDepEntry qe;
qe.Item = *li;
qe.DependerIndex = depender_index;
this->SharedDepQueue.push(qe);
}
}
//----------------------------------------------------------------------------
void cmComputeLinkDepends::HandleSharedDependency(SharedDepEntry const& dep)
{
if(std::vector<std::string> const* libs =
target->GetImportedLinkLibraries(this->Config))
// Check if the target already has an entry.
std::map<cmStdString, int>::iterator lei =
this->LinkEntryIndex.find(dep.Item);
if(lei == this->LinkEntryIndex.end())
{
this->AddLinkEntries(depender_index, *libs);
// Allocate a spot for the item entry.
lei = this->AllocateLinkEntry(dep.Item);
// Initialize the item entry.
LinkEntry& entry = this->EntryList[lei->second];
entry.Item = dep.Item;
entry.Target = this->Makefile->FindTargetToUse(dep.Item.c_str());
// This item was added specifically because it is a dependent
// shared library. It may get special treatment
// in cmComputeLinkInformation.
entry.IsSharedDep = true;
}
// Get the link entry for this target.
int index = lei->second;
LinkEntry& entry = this->EntryList[index];
// This shared library dependency must be preceded by the item that
// listed it.
this->EntryConstraintGraph[index].insert(dep.DependerIndex);
// Target items may have their own dependencies.
if(entry.Target)
{
if(cmTargetLinkInterface const* interface =
entry.Target->GetLinkInterface(this->Config))
{
// We use just the shared dependencies, not the interface.
this->QueueSharedDependencies(index, interface->SharedDeps);
}
}
}
......
......@@ -41,8 +41,10 @@ public:
{
std::string Item;
cmTarget* Target;
LinkEntry(): Item(), Target(0) {}
LinkEntry(LinkEntry const& r): Item(r.Item), Target(r.Target) {}
bool IsSharedDep;
LinkEntry(): Item(), Target(0), IsSharedDep(false) {}
LinkEntry(LinkEntry const& r):
Item(r.Item), Target(r.Target), IsSharedDep(r.IsSharedDep) {}
};
typedef std::vector<LinkEntry> EntryVector;
......@@ -65,8 +67,9 @@ private:
typedef cmTarget::LinkLibraryVectorType LinkLibraryVectorType;
std::map<cmStdString, int>::iterator
AllocateLinkEntry(std::string const& item);
int AddLinkEntry(std::string const& item);
void AddImportedLinkEntries(int depender_index, cmTarget* target);
void AddVarLinkEntries(int depender_index, const char* value);
void AddTargetLinkEntries(int depender_index,
LinkLibraryVectorType const& libs);
......@@ -86,6 +89,19 @@ private:
std::queue<BFSEntry> BFSQueue;
void FollowLinkEntry(BFSEntry const&);
// Shared libraries that are included only because they are
// dependencies of other shared libraries, not because they are part
// of the interface.
struct SharedDepEntry
{
std::string Item;
int DependerIndex;
};
std::queue<SharedDepEntry> SharedDepQueue;
void QueueSharedDependencies(int depender_index,
std::vector<std::string> const& deps);
void HandleSharedDependency(SharedDepEntry const& dep);
// Dependency inferral for each link item.
struct DependSet: public std::set<int> {};
struct DependSetList: public std::vector<DependSet> {};
......
......@@ -234,6 +234,21 @@ cmComputeLinkInformation
// Setup framework support.
this->ComputeFrameworkInfo();
// Choose a mode for dealing with shared library dependencies.
this->SharedDependencyMode = SharedDepModeNone;
if(const char* mode =
this->Makefile->GetDefinition("CMAKE_DEPENDENT_SHARED_LIBRARY_MODE"))
{
if(strcmp(mode, "LINK") == 0)
{
this->SharedDependencyMode = SharedDepModeLink;
}
else if(strcmp(mode, "DIR") == 0)
{
this->SharedDependencyMode = SharedDepModeDir;
}
}
// Get the implicit link directories for this platform.
if(const char* implicitLinks =
(this->Makefile->GetDefinition
......@@ -335,7 +350,7 @@ bool cmComputeLinkInformation::Compute()
lei = linkEntries.begin();
lei != linkEntries.end(); ++lei)
{
this->AddItem(lei->Item, lei->Target);
this->AddItem(lei->Item, lei->Target, lei->IsSharedDep);
}
// Restore the target link type so the correct system runtime
......@@ -358,8 +373,14 @@ bool cmComputeLinkInformation::Compute()
//----------------------------------------------------------------------------
void cmComputeLinkInformation::AddItem(std::string const& item,
cmTarget* tgt)
cmTarget* tgt, bool isSharedDep)
{
// If dropping shared library dependencies, ignore them.
if(isSharedDep && this->SharedDependencyMode == SharedDepModeNone)
{
return;
}
// Compute the proper name to use to link this library.
const char* config = this->Config;
bool impexe = (tgt && tgt->IsExecutableWithExports());
......@@ -370,12 +391,6 @@ void cmComputeLinkInformation::AddItem(std::string const& item,
return;
}
// Keep track of shared libraries linked.
if(tgt && tgt->GetType() == cmTarget::SHARED_LIBRARY)
{
this->SharedLibrariesLinked.insert(tgt);
}
if(tgt && (tgt->GetType() == cmTarget::STATIC_LIBRARY ||
tgt->GetType() == cmTarget::SHARED_LIBRARY ||
tgt->GetType() == cmTarget::MODULE_LIBRARY ||
......@@ -401,6 +416,14 @@ void cmComputeLinkInformation::AddItem(std::string const& item,
(this->UseImportLibrary &&
(impexe || tgt->GetType() == cmTarget::SHARED_LIBRARY));
// Handle shared dependencies in directory mode.
if(isSharedDep && this->SharedDependencyMode == SharedDepModeDir)
{
std::string dir = tgt->GetDirectory(config, implib);
this->SharedDependencyDirectories.push_back(dir);
return;
}
// Pass the full path to the target file.
std::string lib = tgt->GetFullPath(config, implib);
this->Depends.push_back(lib);
......@@ -411,6 +434,7 @@ void cmComputeLinkInformation::AddItem(std::string const& item,
// link.
std::string fw = tgt->GetDirectory(config, implib);
this->AddFrameworkItem(fw);
this->SharedLibrariesLinked.insert(tgt);
}
else
{
......@@ -705,6 +729,12 @@ void cmComputeLinkInformation::AddTargetItem(std::string const& item,
this->Items.push_back(Item(this->LibLinkFileFlag, false));
}
// Keep track of shared library targets linked.
if(target->GetType() == cmTarget::SHARED_LIBRARY)
{
this->SharedLibrariesLinked.insert(target);
}
// Now add the full path to the library.
this->Items.push_back(Item(item, true));
}
......@@ -991,6 +1021,18 @@ void cmComputeLinkInformation::ComputeLinkerSearchDirectories()
{
this->AddLinkerSearchDirectories(this->OldLinkDirs);
}
// Help the linker find dependent shared libraries.
if(this->SharedDependencyMode == SharedDepModeDir)
{
// TODO: These directories should probably be added to the runtime
// path ordering analysis. However they are a bit different.
// They should be placed both on the -L path and in the rpath.
// The link-with-runtime-path feature above should be replaced by
// this.
this->AddLinkerSearchDirectories(this->SharedDependencyDirectories);
}
}
//----------------------------------------------------------------------------
......
......@@ -58,7 +58,7 @@ public:
std::string GetChrpathTool();
std::set<cmTarget*> const& GetSharedLibrariesLinked();
private:
void AddItem(std::string const& item, cmTarget* tgt);
void AddItem(std::string const& item, cmTarget* tgt, bool isSharedDep);
// Output information.
ItemVector Items;
......@@ -78,6 +78,14 @@ private:
const char* Config;
const char* LinkLanguage;
// Modes for dealing with dependent shared libraries.
enum SharedDepMode
{
SharedDepModeNone, // Drop
SharedDepModeDir, // Use in runtime information
SharedDepModeLink // List file on link line
};
// System info.
bool UseImportLibrary;
const char* LoaderFlag;
......@@ -88,6 +96,7 @@ private:
std::string RuntimeSep;
std::string RuntimeAlways;
bool RuntimeUseChrpath;
SharedDepMode SharedDependencyMode;
// Link type adjustment.
void ComputeLinkTypeInfo();
......@@ -134,6 +143,7 @@ private:
void AddLinkerSearchDirectories(std::vector<std::string> const& dirs);
std::set<cmStdString> DirectoriesEmmitted;
std::set<cmStdString> ImplicitLinkDirs;
std::vector<std::string> SharedDependencyDirectories;
// Linker search path compatibility mode.
std::vector<std::string> OldLinkDirs;
......
......@@ -1104,5 +1104,7 @@ void cmDocumentVariables::DefineVariables(cmake* cm)
cmProperty::VARIABLE,0,0);
cm->DefineProperty("CMAKE_SHARED_MODULE_RUNTIME_<LANG>_FLAG_SEP",
cmProperty::VARIABLE,0,0);
cm->DefineProperty("CMAKE_DEPENDENT_SHARED_LIBRARY_MODE",
cmProperty::VARIABLE,0,0);
}
......@@ -139,7 +139,12 @@ cmExportFileGenerator
target->GetLinkInterface(config))
{
// This target provides a link interface, so use it.
this->SetImportLinkProperties(suffix, target, *interface, properties);
this->SetImportLinkProperty(suffix, target,
"IMPORTED_LINK_INTERFACE_LIBRARIES",
interface->Libraries, properties);
this->SetImportLinkProperty(suffix, target,
"IMPORTED_LINK_DEPENDENT_LIBRARIES",
interface->SharedDeps, properties);
}
else if(target->GetType() == cmTarget::STATIC_LIBRARY ||
target->GetType() == cmTarget::SHARED_LIBRARY)
......@@ -183,17 +188,26 @@ cmExportFileGenerator
}
// Store the entries in the property.
this->SetImportLinkProperties(suffix, target, actual_libs, properties);
this->SetImportLinkProperty(suffix, target,
"IMPORTED_LINK_INTERFACE_LIBRARIES",
actual_libs, properties);
}
//----------------------------------------------------------------------------
void
cmExportFileGenerator
::SetImportLinkProperties(std::string const& suffix,
cmTarget* target,
std::vector<std::string> const& libs,
ImportPropertyMap& properties)
::SetImportLinkProperty(std::string const& suffix,
cmTarget* target,
const char* propName,
std::vector<std::string> const& libs,
ImportPropertyMap& properties)
{
// Skip the property if there are no libraries.
if(libs.empty())
{
return;
}
// Get the makefile in which to lookup target information.
cmMakefile* mf = target->GetMakefile();
......@@ -233,6 +247,9 @@ cmExportFileGenerator
// known here. This is probably user-error.
this->ComplainAboutMissingTarget(target, li->c_str());
}
// Assume the target will be exported by another command.
// Append it with the export namespace.
link_libs += this->Namespace;
link_libs += *li;
}
}
......@@ -244,7 +261,7 @@ cmExportFileGenerator
}
// Store the property.
std::string prop = "IMPORTED_LINK_LIBRARIES";
std::string prop = propName;
prop += suffix;
properties[prop] = link_libs;
}
......
......@@ -70,10 +70,10 @@ protected:
void SetImportLinkProperties(const char* config,
std::string const& suffix, cmTarget* target,
ImportPropertyMap& properties);
void SetImportLinkProperties(std::string const& suffix,
cmTarget* target,
std::vector<std::string> const& libs,
ImportPropertyMap& properties);
void SetImportLinkProperty(std::string const& suffix,
cmTarget* target, const char* propName,
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;
......
......@@ -188,15 +188,38 @@ void cmTarget::DefineProperties(cmake *cm)
"from which the target is imported.");
cm->DefineProperty
("IMPORTED_LINK_LIBRARIES", cmProperty::TARGET,
"Transitive link dependencies of an IMPORTED target.",
"Lists dependencies that must be linked when an IMPORTED library "
("IMPORTED_LINK_DEPENDENT_LIBRARIES", cmProperty::TARGET,
"Dependent shared libraries of an imported shared library.",
"Shared libraries may be linked to other shared libraries as part "
"of their implementation. On some platforms the linker searches "
"for the dependent libraries of shared libraries they are including "
"in the link. CMake gives the paths to these libraries to the linker "
"by listing them on the link line explicitly. This property lists "
"the dependent shared libraries of an imported library. The list "
"should be disjoint from the list of interface libraries in the "
"IMPORTED_LINK_INTERFACE_LIBRARIES property. On platforms requiring "
"dependent shared libraries to be found at link time CMake uses this "
"list to add the dependent libraries to the link command line.");
cm->DefineProperty
("IMPORTED_LINK_DEPENDENT_LIBRARIES_<CONFIG>", cmProperty::TARGET,
"Per-configuration version of IMPORTED_LINK_DEPENDENT_LIBRARIES.",
"This property is used when loading settings for the <CONFIG> "
"configuration of an imported target. "
"Configuration names correspond to those provided by the project "
"from which the target is imported.");
cm->DefineProperty
("IMPORTED_LINK_INTERFACE_LIBRARIES", cmProperty::TARGET,
"Transitive link interface of an IMPORTED target.",
"Lists libraries whose interface is included when an IMPORTED library "
"target is linked to another target. "
"The libraries will be included on the link line for the target. "
"Ignored for non-imported targets.");
cm->DefineProperty
("IMPORTED_LINK_LIBRARIES_<CONFIG>", cmProperty::TARGET,
"Per-configuration version of IMPORTED_LINK_LIBRARIES property.",
("IMPORTED_LINK_INTERFACE_LIBRARIES_<CONFIG>", cmProperty::TARGET,
"Per-configuration version of IMPORTED_LINK_INTERFACE_LIBRARIES.",
"This property is used when loading settings for the <CONFIG> "
"configuration of an imported target. "
"Configuration names correspond to those provided by the project "
......@@ -3045,43 +3068,56 @@ void cmTarget::ComputeImportInfo(std::string const& desired_config,
}
}
// Get the link dependencies.
// Get the link interface.
{
std::string linkProp = "IMPORTED_LINK_LIBRARIES";
std::string linkProp = "IMPORTED_LINK_INTERFACE_LIBRARIES";
linkProp += suffix;
if(const char* config_libs = this->GetProperty(linkProp.c_str()))
{
cmSystemTools::ExpandListArgument(config_libs, info.LinkLibraries);
cmSystemTools::ExpandListArgument(config_libs,
info.LinkInterface.Libraries);
}
else if(const char* libs = this->GetProperty("IMPORTED_LINK_LIBRARIES"))
else if(const char* libs =
this->GetProperty("IMPORTED_LINK_INTERFACE_LIBRARIES"))
{
cmSystemTools::ExpandListArgument(libs, info.LinkLibraries);
cmSystemTools::ExpandListArgument(libs,
info.LinkInterface.Libraries);
}
}
}
//----------------------------------------------------------------------------
std::vector<std::string> const*
cmTarget::GetImportedLinkLibraries(const char* config)
{
if(cmTarget::ImportInfo const* info = this->GetImportInfo(config))
// Get the link dependencies.
{
std::string linkProp = "IMPORTED_LINK_DEPENDENT_LIBRARIES";
linkProp += suffix;
if(const char* config_libs = this->GetProperty(linkProp.c_str()))
{
return &info->LinkLibraries;
cmSystemTools::ExpandListArgument(config_libs,
info.LinkInterface.SharedDeps);
}
else
else if(const char* libs =
this->GetProperty("IMPORTED_LINK_DEPENDENT_LIBRARIES"))
{
return 0;
cmSystemTools::ExpandListArgument(libs, info.LinkInterface.SharedDeps);
}
}
}
//----------------------------------------------------------------------------
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 &&
// Imported targets have their own link interface.
if(this->IsImported())
{
if(cmTarget::ImportInfo const* info = this->GetImportInfo(config))
{
return &info->LinkInterface;
}
return 0;
}
// Link interfaces are supported only for shared libraries and
// executables that export symbols.
if((this->GetType() != cmTarget::SHARED_LIBRARY &&
!this->IsExecutableWithExports()))
{
return 0;
......@@ -3139,13 +3175,77 @@ cmTargetLinkInterface* cmTarget::ComputeLinkInterface(const char* config)
return 0;
}
// Return the interface libraries even if the list is empty.
if(cmTargetLinkInterface* interface = new cmTargetLinkInterface)
// Allocate the interface.
cmTargetLinkInterface* interface = new cmTargetLinkInterface;
if(!interface)
{
cmSystemTools::ExpandListArgument(libs, *interface);
return interface;
return 0;
}
// Expand the list of libraries in the interface.
cmSystemTools::ExpandListArgument(libs, interface->Libraries);
// Now we need to construct a list of shared library dependencies
// not included in the interface.
if(this->GetType() == cmTarget::SHARED_LIBRARY)
{
// Use a set to keep track of what libraries have been emitted to
// either list.
std::set<cmStdString> emitted;
for(std::vector<std::string>::const_iterator
li = interface->Libraries.begin();
li != interface->Libraries.end(); ++li)
{
emitted.insert(*li);
}
// Compute which library configuration to link.
cmTarget::LinkLibraryType linkType = cmTarget::OPTIMIZED;
if(config && cmSystemTools::UpperCase(config) == "DEBUG")
{
linkType = cmTarget::DEBUG;
}
// Construct the list of libs linked for this configuration.
cmTarget::LinkLibraryVectorType const& libs =
this->GetOriginalLinkLibraries();
for(cmTarget::LinkLibraryVectorType::const_iterator li = libs.begin();
li != libs.end(); ++li)
{
// Skip entries that will resolve to the target itself, are empty,
// or are not meant for this configuration.
if(li->first == this->GetName() || li->first.empty() ||
!(li->