Commit 5594ad48 authored by Brad King's avatar Brad King 💬
Browse files

ENH: Updated exporting and importing of targets to support libraries and configurations.

  - Created cmExportFileGenerator hierarchy to implement export file generation
  - Installed exports use per-config import files loaded by a central one.
  - Include soname of shared libraries in import information
  - Renamed PREFIX to NAMESPACE in INSTALL(EXPORT) and EXPORT() commands
  - Move addition of CMAKE_INSTALL_PREFIX to destinations to install generators
  - Import files compute the installation prefix relative to their location when loaded
  - Add mapping of importer configurations to importee configurations
  - Rename IMPORT targets to IMPORTED targets to distinguish from windows import libraries
  - Scope IMPORTED targets within directories to isolate them
  - Place all properties created by import files in the IMPORTED namespace
  - Document INSTALL(EXPORT) and EXPORT() commands.
  - Document IMPORTED signature of add_executable and add_library
  - Enable finding of imported targets in cmComputeLinkDepends
parent a7cb9d11
......@@ -121,6 +121,12 @@ SET(SRCS
cmExprLexer.cxx
cmExprParser.cxx
cmExprParserHelper.cxx
cmExportBuildFileGenerator.h
cmExportBuildFileGenerator.cxx
cmExportFileGenerator.h
cmExportFileGenerator.cxx
cmExportInstallFileGenerator.h
cmExportInstallFileGenerator.cxx
cmExtraEclipseCDT4Generator.cxx
cmExtraEclipseCDT4Generator.h
cmFileTimeComparison.cxx
......
......@@ -32,7 +32,7 @@ bool cmAddDependenciesCommand
cmTarget* target =
this->GetMakefile()->GetLocalGenerator()->
GetGlobalGenerator()->FindTarget(0, target_name.c_str(), false);
GetGlobalGenerator()->FindTarget(0, target_name.c_str());
if(target)
{
std::vector<std::string>::const_iterator s = args.begin();
......
......@@ -51,7 +51,7 @@ bool cmAddExecutableCommand
++s;
excludeFromAll = true;
}
else if(*s == "IMPORT")
else if(*s == "IMPORTED")
{
++s;
importTarget = true;
......@@ -61,12 +61,60 @@ bool cmAddExecutableCommand
break;
}
}
if (importTarget)
// Special modifiers are not allowed with IMPORTED signature.
if(importTarget && (use_win32 || use_macbundle || excludeFromAll))
{
if(use_win32)
{
this->SetError("may not be given WIN32 for an IMPORTED target.");
}
else if(use_macbundle)
{
this->SetError(
"may not be given MACOSX_BUNDLE for an IMPORTED target.");
}
else // if(excludeFromAll)
{
this->SetError(
"may not be given EXCLUDE_FROM_ALL for an IMPORTED target.");
}
return false;
}
// Check for an existing target with this name.
cmTarget* existing = this->Makefile->FindTargetToUse(exename.c_str());
if(importTarget)
{
this->Makefile->AddNewTarget(cmTarget::EXECUTABLE, exename.c_str(), true);
// Make sure the target does not already exist.
if(existing)
{
cmOStringStream e;
e << "cannot create imported target \"" << exename
<< "\" because another target with the same name already exists.";
this->SetError(e.str().c_str());
return false;
}
// Create the imported target.
this->Makefile->AddImportedTarget(exename.c_str(), cmTarget::EXECUTABLE);
return true;
}
else
{
// Make sure the target does not conflict with an imported target.
// This should really enforce global name uniqueness for targets
// built within the project too, but that may break compatiblity
// with projects in which it was accidentally working.
if(existing && existing->IsImported())
{
cmOStringStream e;
e << "cannot create target \"" << exename
<< "\" because an imported target with the same name already exists.";
this->SetError(e.str().c_str());
return false;
}
}
if (s == args.end())
{
......
......@@ -90,6 +90,24 @@ public:
"If EXCLUDE_FROM_ALL is given the target will not be built by default. "
"It will be built only if the user explicitly builds the target or "
"another target that requires the target depends on it."
"\n"
"The add_executable command can also create IMPORTED executable "
"targets using this signature:\n"
" add_executable(<name> IMPORTED)\n"
"An IMPORTED executable target references an executable file located "
"outside the project. "
"No rules are generated to build it. "
"The target name has scope in the directory in which it is created "
"and below. "
"It may be referenced like any target built within the project. "
"IMPORTED executables are useful for convenient reference from "
"commands like add_custom_command. "
"Details about the imported executable are specified by setting "
"properties whose names begin in \"IMPORTED_\". "
"The most important such property is IMPORTED_LOCATION "
"(and its per-configuration version IMPORTED_LOCATION_<CONFIG>) "
"which specifies the location of the main executable file on disk. "
"See documentation of the IMPORTED_* properties for more information."
;
}
......
......@@ -46,6 +46,7 @@ bool cmAddLibraryCommand
// If the second argument is "SHARED" or "STATIC", then it controls
// the type of library. Otherwise, it is treated as a source or
// source list name. There may be two keyword arguments, check for them
bool haveSpecifiedType = false;
while ( s != args.end() )
{
std::string libType = *s;
......@@ -53,23 +54,26 @@ bool cmAddLibraryCommand
{
++s;
type = cmTarget::STATIC_LIBRARY;
haveSpecifiedType = true;
}
else if(libType == "SHARED")
{
++s;
type = cmTarget::SHARED_LIBRARY;
haveSpecifiedType = true;
}
else if(libType == "MODULE")
{
++s;
type = cmTarget::MODULE_LIBRARY;
haveSpecifiedType = true;
}
else if(*s == "EXCLUDE_FROM_ALL")
{
++s;
excludeFromAll = true;
}
else if(*s == "IMPORT")
else if(*s == "IMPORTED")
{
++s;
importTarget = true;
......@@ -98,11 +102,46 @@ bool cmAddLibraryCommand
type = cmTarget::STATIC_LIBRARY;
}
if (importTarget)
// The IMPORTED signature requires a type to be specified explicitly.
if(importTarget && !haveSpecifiedType)
{
this->Makefile->AddNewTarget(type, libName.c_str(), true);
this->SetError("called with IMPORTED argument but no library type.");
return false;
}
// Check for an existing target with this name.
cmTarget* existing = this->Makefile->FindTargetToUse(libName.c_str());
if(importTarget)
{
// Make sure the target does not already exist.
if(existing)
{
cmOStringStream e;
e << "cannot create imported target \"" << libName
<< "\" because another target with the same name already exists.";
this->SetError(e.str().c_str());
return false;
}
// Create the imported target.
this->Makefile->AddImportedTarget(libName.c_str(), type);
return true;
}
else
{
// Make sure the target does not conflict with an imported target.
// This should really enforce global name uniqueness for targets
// built within the project too, but that may break compatiblity
// with projects in which it was accidentally working.
if(existing && existing->IsImported())
{
cmOStringStream e;
e << "cannot create target \"" << libName
<< "\" because an imported target with the same name already exists.";
this->SetError(e.str().c_str());
return false;
}
}
if (s == args.end())
{
......
......@@ -73,7 +73,26 @@ public:
"to STATIC.\n"
"If EXCLUDE_FROM_ALL is given the target will not be built by default. "
"It will be built only if the user explicitly builds the target or "
"another target that requires the target depends on it.";
"another target that requires the target depends on it."
"\n"
"The add_library command can also create IMPORTED library "
"targets using this signature:\n"
" add_library(<name> <SHARED|STATIC|MODULE> IMPORTED)\n"
"An IMPORTED library target references a library file located "
"outside the project. "
"No rules are generated to build it. "
"The target name has scope in the directory in which it is created "
"and below. "
"It may be referenced like any target built within the project. "
"IMPORTED libraries are useful for convenient reference from "
"commands like target_link_libraries. "
"Details about the imported library are specified by setting "
"properties whose names begin in \"IMPORTED_\". "
"The most important such property is IMPORTED_LOCATION "
"(and its per-configuration version IMPORTED_LOCATION_<CONFIG>) "
"which specifies the location of the main library file on disk. "
"See documentation of the IMPORTED_* properties for more information."
;
}
cmTypeMacro(cmAddLibraryCommand, cmCommand);
......
......@@ -158,7 +158,7 @@ std::vector<cmComputeLinkDepends::LinkEntry> const&
cmComputeLinkDepends::Compute()
{
// Follow the link dependencies of the target to be linked.
this->AddLinkEntries(-1, this->Target->GetOriginalLinkLibraries());
this->AddTargetLinkEntries(-1, this->Target->GetOriginalLinkLibraries());
// Complete the breadth-first search of dependencies.
while(!this->BFSQueue.empty())
......@@ -222,8 +222,7 @@ int cmComputeLinkDepends::AddLinkEntry(std::string const& item)
int index = lei->second;
LinkEntry& entry = this->EntryList[index];
entry.Item = item;
entry.Target =
this->GlobalGenerator->FindTarget(0, entry.Item.c_str(), false);
entry.Target = this->Makefile->FindTargetToUse(entry.Item.c_str());
// If the item has dependencies queue it to follow them.
if(entry.Target)
......@@ -264,8 +263,15 @@ void cmComputeLinkDepends::FollowLinkEntry(BFSEntry const& qe)
if(entry.Target)
{
// Follow the target dependencies.
this->AddLinkEntries(depender_index,
entry.Target->GetOriginalLinkLibraries());
if(entry.Target->IsImported())
{
this->AddImportedLinkEntries(depender_index, entry.Target);
}
else
{
this->AddTargetLinkEntries(depender_index,
entry.Target->GetOriginalLinkLibraries());
}
}
else
{
......@@ -274,6 +280,18 @@ void cmComputeLinkDepends::FollowLinkEntry(BFSEntry const& qe)
}
}
//----------------------------------------------------------------------------
void cmComputeLinkDepends::AddImportedLinkEntries(int depender_index,
cmTarget* target)
{
if(std::vector<std::string> const* libs =
target->GetImportedLinkLibraries(this->Config))
{
this->AddLinkEntries(depender_index, *libs);
}
}
//----------------------------------------------------------------------------
void cmComputeLinkDepends::AddVarLinkEntries(int depender_index,
const char* value)
{
......@@ -283,39 +301,49 @@ void cmComputeLinkDepends::AddVarLinkEntries(int depender_index,
std::vector<std::string> deplist;
cmSystemTools::ExpandListArgument(value, deplist);
// Construct the vector of type/value pairs from the variable.
LinkLibraryVectorType libs;
cmTarget::LinkLibraryType linkType = cmTarget::GENERAL;
// Compute which library configuration to link.
cmTarget::LinkLibraryType linkType = cmTarget::OPTIMIZED;
if(this->Config && cmSystemTools::UpperCase(this->Config) == "DEBUG")
{
linkType = cmTarget::DEBUG;
}
// Look for entries meant for this configuration.
std::vector<std::string> actual_libs;
cmTarget::LinkLibraryType llt = cmTarget::GENERAL;
for(std::vector<std::string>::const_iterator di = deplist.begin();
di != deplist.end(); ++di)
{
if(*di == "debug")
{
linkType = cmTarget::DEBUG;
llt = cmTarget::DEBUG;
}
else if(*di == "optimized")
{
linkType = cmTarget::OPTIMIZED;
llt = cmTarget::OPTIMIZED;
}
else if(*di == "general")
{
linkType = cmTarget::GENERAL;
llt = cmTarget::GENERAL;
}
else if(!di->empty())
{
cmTarget::LibraryID lib(*di, linkType);
libs.push_back(lib);
if(llt == cmTarget::GENERAL || llt == linkType)
{
actual_libs.push_back(*di);
}
linkType = cmTarget::GENERAL;
}
}
// Add the entries from this list.
this->AddLinkEntries(depender_index, libs);
this->AddLinkEntries(depender_index, actual_libs);
}
//----------------------------------------------------------------------------
void cmComputeLinkDepends::AddLinkEntries(int depender_index,
LinkLibraryVectorType const& libs)
void
cmComputeLinkDepends::AddTargetLinkEntries(int depender_index,
LinkLibraryVectorType const& libs)
{
// Compute which library configuration to link.
cmTarget::LinkLibraryType linkType = cmTarget::OPTIMIZED;
......@@ -324,23 +352,42 @@ void cmComputeLinkDepends::AddLinkEntries(int depender_index,
linkType = cmTarget::DEBUG;
}
// Look for entries meant for this configuration.
std::vector<std::string> actual_libs;
for(cmTarget::LinkLibraryVectorType::const_iterator li = libs.begin();
li != libs.end(); ++li)
{
if(li->second == cmTarget::GENERAL || li->second == linkType)
{
actual_libs.push_back(li->first);
}
}
// Add these entries.
this->AddLinkEntries(depender_index, actual_libs);
}
//----------------------------------------------------------------------------
void
cmComputeLinkDepends::AddLinkEntries(int depender_index,
std::vector<std::string> const& libs)
{
// Track inferred dependency sets implied by this list.
std::map<int, DependSet> dependSets;
// Loop over the libraries linked directly by the target.
for(cmTarget::LinkLibraryVectorType::const_iterator li = libs.begin();
// Loop over the libraries linked directly by the depender.
for(std::vector<std::string>::const_iterator li = libs.begin();
li != libs.end(); ++li)
{
// Skip entries that will resolve to the target getting linked.
// Skip libraries not meant for the current configuration.
if(li->first == this->Target->GetName() || li->first.empty() ||
!(li->second == cmTarget::GENERAL || li->second == linkType))
// Skip entries that will resolve to the target getting linked or
// are empty.
if(*li == this->Target->GetName() || li->empty())
{
continue;
}
// Add a link entry for this item.
int dependee_index = this->AddLinkEntry(li->first);
int dependee_index = this->AddLinkEntry(*li);
// The depender must come before the dependee.
if(depender_index >= 0)
......
......@@ -66,9 +66,12 @@ private:
typedef cmTarget::LinkLibraryVectorType LinkLibraryVectorType;
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);
void AddLinkEntries(int depender_index,
LinkLibraryVectorType const& libs);
std::vector<std::string> const& libs);
// One entry for each unique item.
std::vector<LinkEntry> EntryList;
......
......@@ -301,11 +301,8 @@ void cmComputeLinkInformation::AddItem(std::string const& item,
{
// Compute the proper name to use to link this library.
const char* config = this->Config;
bool implib = this->UseImportLibrary;
bool impexe = (tgt &&
tgt->GetType() == cmTarget::EXECUTABLE &&
tgt->GetPropertyAsBool("ENABLE_EXPORTS"));
if(impexe && !implib && !this->LoaderFlag)
bool impexe = (tgt && tgt->IsExecutableWithExports());
if(impexe && !this->UseImportLibrary && !this->LoaderFlag)
{
// Skip linking to executables on platforms with no import
// libraries or loader flags.
......@@ -325,13 +322,18 @@ void cmComputeLinkInformation::AddItem(std::string const& item,
// platform. Add it now.
std::string linkItem;
linkItem = this->LoaderFlag;
std::string exe = tgt->GetFullPath(config, implib);
std::string exe = tgt->GetFullPath(config, this->UseImportLibrary);
linkItem += exe;
this->Items.push_back(Item(linkItem, true));
this->Depends.push_back(exe);
}
else
{
// Decide whether to use an import library.
bool implib =
(this->UseImportLibrary &&
(impexe || tgt->GetType() == cmTarget::SHARED_LIBRARY));
// Pass the full path to the target file.
std::string lib = tgt->GetFullPath(config, implib);
this->Depends.push_back(lib);
......@@ -950,18 +952,8 @@ cmComputeLinkInformation::AddLibraryRuntimeInfo(std::string const& fullPath,
// Try to get the soname of the library. Only files with this name
// could possibly conflict.
std::string soName;
const char* soname = 0;
if(!target->IsImported())
{
std::string name;
std::string realName;
std::string impName;
std::string pdbName;
target->GetLibraryNames(name, soName, realName, impName, pdbName,
this->Config);
soname = soName.c_str();
}
std::string soName = target->GetSOName(this->Config);
const char* soname = soName.empty()? 0 : soName.c_str();
// Add the library runtime entry.
this->AddLibraryRuntimeInfo(fullPath, soname);
......
/*=========================================================================
Program: CMake - Cross-Platform Makefile Generator
Module: $RCSfile$
Language: C++
Date: $Date$
Version: $Revision$
Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=========================================================================*/
#include "cmExportBuildFileGenerator.h"
//----------------------------------------------------------------------------
bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
{
// Create all the imported targets.
for(std::vector<cmTarget*>::const_iterator
tei = this->Exports->begin();
tei != this->Exports->end(); ++tei)
{
cmTarget* te = *tei;
this->ExportedTargets.insert(te);
this->GenerateImportTargetCode(os, te);
}
// Generate import file content for each configuration.
for(std::vector<std::string>::const_iterator
ci = this->Configurations.begin();
ci != this->Configurations.end(); ++ci)
{
this->GenerateImportConfig(os, ci->c_str());
}
return true;
}
//----------------------------------------------------------------------------
void
cmExportBuildFileGenerator
::GenerateImportTargetsConfig(std::ostream& os,
const char* config, std::string const& suffix)
{
for(std::vector<cmTarget*>::const_iterator
tei = this->Exports->begin();
tei != this->Exports->end(); ++tei)
{
// Collect import properties for this target.
cmTarget* target = *tei;
ImportPropertyMap properties;
this->SetImportLocationProperty(config, suffix, target, properties);
if(!properties.empty())
{
// Get the rest of the target details.
this->SetImportDetailProperties(config, suffix,
target, properties);
// TOOD: PUBLIC_HEADER_LOCATION
// this->GenerateImportProperty(config, te->HeaderGenerator,
// properties);
// Generate code in the export file.
this->GenerateImportPropertyCode(os, config, target, properties);
}
}
}
//----------------------------------------------------------------------------
void
cmExportBuildFileGenerator
::SetImportLocationProperty(const char* config, std::string const& suffix,
cmTarget* target, ImportPropertyMap& properties)
{
// Get the makefile in which to lookup target information.
cmMakefile* mf = target->GetMakefile();
// Add the main target file.
{
std::string prop = "IMPORTED_LOCATION";
prop += suffix;
std::string value = target->GetFullPath(config, false);
properties[prop] = value;
}
// Check whether this is a DLL platform.
bool dll_platform =
(mf->IsOn("WIN32") || mf->IsOn("CYGWIN") || mf->IsOn("MINGW"));
// Add the import library for windows DLLs.
if(dll_platform &&
(target->GetType() == cmTarget::SHARED_LIBRARY ||
target->IsExecutableWithExports()) &&
mf->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX"))
{
std::string prop = "IMPORTED_IMPLIB";
prop += suffix;
std::string value = target->GetFullPath(config, true);
properties[prop] = value;
}
}
//----------------------------------------------------------------------------
void
cmExportBuildFileGenerator
::ComplainAboutMissingTarget(cmTarget* target, const char* dep)
{
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());
}
/*=========================================================================
Program: CMake - Cross-Platform Makefile Generator
Module: $RCSfile$
Language: C++
Date: $Date$
Version: $Revision$
Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=========================================================================*/
#ifndef cmExportBuildFileGenerator_h
#define cmExportBuildFileGenerator_h
#include "cmExportFileGenerator.h"