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
This commit is contained in:
Brad King 2008-01-31 15:45:31 -05:00
parent 52e75800b4
commit 2cff26fa52
11 changed files with 345 additions and 97 deletions

View File

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

View File

@ -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 =
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)
{
if(std::vector<std::string> const* libs =
target->GetImportedLinkLibraries(this->Config))
for(std::vector<std::string>::const_iterator li = deps.begin();
li != deps.end(); ++li)
{
this->AddLinkEntries(depender_index, *libs);
SharedDepEntry qe;
qe.Item = *li;
qe.DependerIndex = depender_index;
this->SharedDepQueue.push(qe);
}
}
//----------------------------------------------------------------------------
void cmComputeLinkDepends::HandleSharedDependency(SharedDepEntry const& dep)
{
// Check if the target already has an entry.
std::map<cmStdString, int>::iterator lei =
this->LinkEntryIndex.find(dep.Item);
if(lei == this->LinkEntryIndex.end())
{
// 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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -70,8 +70,8 @@ protected:
void SetImportLinkProperties(const char* config,
std::string const& suffix, cmTarget* target,
ImportPropertyMap& properties);
void SetImportLinkProperties(std::string const& suffix,
cmTarget* target,
void SetImportLinkProperty(std::string const& suffix,
cmTarget* target, const char* propName,
std::vector<std::string> const& libs,
ImportPropertyMap& properties);

View File

@ -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.
{
return &info->LinkLibraries;
std::string linkProp = "IMPORTED_LINK_DEPENDENT_LIBRARIES";
linkProp += suffix;
if(const char* config_libs = this->GetProperty(linkProp.c_str()))
{
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->second == cmTarget::GENERAL || li->second == linkType))
{
continue;
}
// Skip entries that have already been emitted into either list.
if(!emitted.insert(li->first).second)
{
continue;
}
// Add this entry if it is a shared library.
if(cmTarget* tgt = this->Makefile->FindTargetToUse(li->first.c_str()))
{
if(tgt->GetType() == cmTarget::SHARED_LIBRARY)
{
interface->SharedDeps.push_back(li->first);
}
}
else
{
// TODO: Recognize shared library file names. Perhaps this
// should be moved to cmComputeLinkInformation, but that creates
// a chicken-and-egg problem since this list is needed for its
// construction.
}
}
}
// Return the completed interface.
return interface;
}
//----------------------------------------------------------------------------

View File

@ -35,9 +35,13 @@ struct cmTargetLinkInformationMap:
~cmTargetLinkInformationMap();
};
struct cmTargetLinkInterface: public std::vector<std::string>
struct cmTargetLinkInterface
{
typedef std::vector<std::string> derived;
// Libraries listed in the interface.
std::vector<std::string> Libraries;
// Shared library dependencies needed for linking on some platforms.
std::vector<std::string> SharedDeps;
};
struct cmTargetLinkInterfaceMap:
@ -218,11 +222,6 @@ public:
bool IsImported() const {return this->IsImportedTarget;}
/** Get link libraries for the given configuration of an imported
target. */
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
@ -487,7 +486,7 @@ private:
std::string Location;
std::string SOName;
std::string ImportLibrary;
std::vector<std::string> LinkLibraries;
cmTargetLinkInterface LinkInterface;
};
typedef std::map<cmStdString, ImportInfo> ImportInfoMapType;
ImportInfoMapType ImportInfoMap;

View File

@ -32,17 +32,11 @@ set_property(TARGET testLib4 PROPERTY FRAMEWORK 1)
add_executable(testExe3 testExe3.c)
set_property(TARGET testExe3 PROPERTY MACOSX_BUNDLE 1)
# Install helper targets that are not part of the interface.
install(
TARGETS testExe2libImp testLib3Imp
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
# Install and export from install tree.
install(
TARGETS testExe1 testLib1 testLib2 testExe2 testLib3 testLib4 testExe3
TARGETS
testExe2libImp testLib3Imp
testExe1 testLib1 testLib2 testExe2 testLib3 testLib4 testExe3
testExe2lib
EXPORT exp
RUNTIME DESTINATION bin
@ -55,6 +49,7 @@ install(EXPORT exp NAMESPACE exp_ DESTINATION lib/exp)
# Export from build tree.
export(TARGETS testExe1 testLib1 testLib2 testLib3
testExe2libImp testLib3Imp
NAMESPACE bld_
FILE ExportBuildTree.cmake
)