Просмотр исходного кода

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.
Brad King 18 лет назад
Родитель
Сommit
7902bc06aa

+ 15 - 10
Source/cmComputeLinkDepends.cxx

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

+ 22 - 4
Source/cmExportBuildFileGenerator.cxx

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

+ 8 - 0
Source/cmExportBuildFileGenerator.h

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

+ 8 - 6
Source/cmExportCommand.cxx

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

+ 5 - 0
Source/cmExportCommand.h

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

+ 58 - 19
Source/cmExportFileGenerator.cxx

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

+ 5 - 0
Source/cmExportFileGenerator.h

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

+ 9 - 5
Source/cmExportInstallFileGenerator.cxx

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

+ 119 - 0
Source/cmTarget.cxx

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

+ 24 - 0
Source/cmTarget.h

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