浏览代码

install,export: Maybe transform OBJECT libraries to INTERFACE libraries

Teach the `install` and `export` commands to support installing and
exporting `OBJECT` libraries without their object files.  Transform
them to `INTERFACE` libraries in such cases.

For `install(TARGETS)`, activate this when no destination for the object
files is specified.  For `export`, activate this only under Xcode with
multiple architectures when we have no well-defined object file
locations to give to clients.
Brad King 7 年之前
父节点
当前提交
ea0ce73a19

+ 7 - 0
Help/command/export.rst

@@ -40,6 +40,13 @@ policy CMP0022 is NEW.  If a library target is included in the export
 but a target to which it links is not included the behavior is
 unspecified.
 
+.. note::
+
+  :ref:`Object Libraries` under :generator:`Xcode` have special handling if
+  multiple architectures are listed in :variable:`CMAKE_OSX_ARCHITECTURES`.
+  In this case they will be exported as :ref:`Interface Libraries` with
+  no object files available to clients.
+
 ::
 
   export(PACKAGE <name>)

+ 5 - 0
Help/command/install.rst

@@ -183,6 +183,11 @@ export called ``<export-name>``.  It must appear before any ``RUNTIME``,
 ``LIBRARY``, ``ARCHIVE``, or ``OBJECTS`` options.  To actually install the
 export file itself, call ``install(EXPORT)``, documented below.
 
+:ref:`Interface Libraries` may be listed among the targets to install.
+They install no artifacts but will be included in an associated ``EXPORT``.
+If :ref:`Object Libraries` are listed but given no destination for their
+object files, they will be exported as :ref:`Interface Libraries`.
+
 Installing a target with the :prop_tgt:`EXCLUDE_FROM_ALL` target property
 set to ``TRUE`` has undefined behavior.
 

+ 2 - 1
Source/cmExportBuildAndroidMKGenerator.cxx

@@ -40,7 +40,8 @@ void cmExportBuildAndroidMKGenerator::GenerateExpectedTargetsCode(
 }
 
 void cmExportBuildAndroidMKGenerator::GenerateImportTargetCode(
-  std::ostream& os, const cmGeneratorTarget* target)
+  std::ostream& os, cmGeneratorTarget const* target,
+  cmStateEnums::TargetType /*targetType*/)
 {
   std::string targetName = this->Namespace;
   targetName += target->GetExportName();

+ 4 - 2
Source/cmExportBuildAndroidMKGenerator.h

@@ -11,6 +11,7 @@
 
 #include "cmExportBuildFileGenerator.h"
 #include "cmExportFileGenerator.h"
+#include "cmStateTypes.h"
 
 class cmGeneratorTarget;
 
@@ -47,8 +48,9 @@ protected:
   void GenerateImportHeaderCode(std::ostream& os,
                                 const std::string& config = "") override;
   void GenerateImportFooterCode(std::ostream& os) override;
-  void GenerateImportTargetCode(std::ostream& os,
-                                const cmGeneratorTarget* target) override;
+  void GenerateImportTargetCode(
+    std::ostream& os, cmGeneratorTarget const* target,
+    cmStateEnums::TargetType /*targetType*/) override;
   void GenerateExpectedTargetsCode(
     std::ostream& os, const std::string& expectedTargets) override;
   void GenerateImportPropertyCode(

+ 18 - 4
Source/cmExportBuildFileGenerator.cxx

@@ -59,7 +59,7 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
           this->LG->GetMakefile()->GetBacktrace());
         return false;
       }
-      if (te->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+      if (this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY) {
         this->GenerateRequiredCMakeVersion(os, "3.0.0");
       }
     }
@@ -71,7 +71,7 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
 
   // Create all the imported targets.
   for (cmGeneratorTarget* gte : this->Exports) {
-    this->GenerateImportTargetCode(os, gte);
+    this->GenerateImportTargetCode(os, gte, this->GetExportTargetType(gte));
 
     gte->Target->AppendBuildInterfaceIncludes();
 
@@ -128,12 +128,13 @@ void cmExportBuildFileGenerator::GenerateImportTargetsConfig(
     // Collect import properties for this target.
     ImportPropertyMap properties;
 
-    if (target->GetType() != cmStateEnums::INTERFACE_LIBRARY) {
+    if (this->GetExportTargetType(target) != cmStateEnums::INTERFACE_LIBRARY) {
       this->SetImportLocationProperty(config, suffix, target, properties);
     }
     if (!properties.empty()) {
       // Get the rest of the target details.
-      if (target->GetType() != cmStateEnums::INTERFACE_LIBRARY) {
+      if (this->GetExportTargetType(target) !=
+          cmStateEnums::INTERFACE_LIBRARY) {
         this->SetImportDetailProperties(config, suffix, target, properties,
                                         missingTargets);
         this->SetImportLinkInterface(config, suffix,
@@ -153,6 +154,19 @@ void cmExportBuildFileGenerator::GenerateImportTargetsConfig(
   }
 }
 
+cmStateEnums::TargetType cmExportBuildFileGenerator::GetExportTargetType(
+  cmGeneratorTarget const* target) const
+{
+  cmStateEnums::TargetType targetType = target->GetType();
+  // An object library exports as an interface library if we cannot
+  // tell clients where to find the objects.
+  if (targetType == cmStateEnums::OBJECT_LIBRARY &&
+      !this->LG->GetGlobalGenerator()->HasKnownObjectFileLocation(nullptr)) {
+    targetType = cmStateEnums::INTERFACE_LIBRARY;
+  }
+  return targetType;
+}
+
 void cmExportBuildFileGenerator::SetExportSet(cmExportSet* exportSet)
 {
   this->ExportSet = exportSet;

+ 3 - 0
Source/cmExportBuildFileGenerator.h

@@ -6,6 +6,7 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include "cmExportFileGenerator.h"
+#include "cmStateTypes.h"
 
 #include <iosfwd>
 #include <string>
@@ -53,6 +54,8 @@ protected:
   void GenerateImportTargetsConfig(
     std::ostream& os, const std::string& config, std::string const& suffix,
     std::vector<std::string>& missingTargets) override;
+  cmStateEnums::TargetType GetExportTargetType(
+    cmGeneratorTarget const* target) const;
   void HandleMissingTarget(std::string& link_libs,
                            std::vector<std::string>& missingTargets,
                            cmGeneratorTarget* depender,

+ 0 - 11
Source/cmExportCommand.cxx

@@ -146,17 +146,6 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
       }
 
       if (cmTarget* target = gg->FindTarget(currentTarget)) {
-        if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
-          std::string reason;
-          if (!this->Makefile->GetGlobalGenerator()
-                 ->HasKnownObjectFileLocation(&reason)) {
-            std::ostringstream e;
-            e << "given OBJECT library \"" << currentTarget
-              << "\" which may not be exported" << reason << ".";
-            this->SetError(e.str());
-            return false;
-          }
-        }
         if (target->GetType() == cmStateEnums::UTILITY) {
           this->SetError("given custom target \"" + currentTarget +
                          "\" which may not be exported.");

+ 4 - 2
Source/cmExportFileGenerator.cxx

@@ -901,8 +901,10 @@ void cmExportFileGenerator::GenerateExpectedTargetsCode(
         "\n\n";
   /* clang-format on */
 }
+
 void cmExportFileGenerator::GenerateImportTargetCode(
-  std::ostream& os, const cmGeneratorTarget* target)
+  std::ostream& os, cmGeneratorTarget const* target,
+  cmStateEnums::TargetType targetType)
 {
   // Construct the imported target name.
   std::string targetName = this->Namespace;
@@ -911,7 +913,7 @@ void cmExportFileGenerator::GenerateImportTargetCode(
 
   // Create the imported target.
   os << "# Create imported target " << targetName << "\n";
-  switch (target->GetType()) {
+  switch (targetType) {
     case cmStateEnums::EXECUTABLE:
       os << "add_executable(" << targetName << " IMPORTED)\n";
       break;

+ 3 - 1
Source/cmExportFileGenerator.h

@@ -6,6 +6,7 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include "cmGeneratorExpression.h"
+#include "cmStateTypes.h"
 #include "cmVersion.h"
 #include "cmVersionConfig.h"
 
@@ -76,7 +77,8 @@ protected:
   virtual void GenerateImportFooterCode(std::ostream& os);
   void GenerateImportVersionCode(std::ostream& os);
   virtual void GenerateImportTargetCode(std::ostream& os,
-                                        cmGeneratorTarget const* target);
+                                        cmGeneratorTarget const* target,
+                                        cmStateEnums::TargetType targetType);
   virtual void GenerateImportPropertyCode(std::ostream& os,
                                           const std::string& config,
                                           cmGeneratorTarget const* target,

+ 2 - 1
Source/cmExportInstallAndroidMKGenerator.cxx

@@ -55,7 +55,8 @@ void cmExportInstallAndroidMKGenerator::GenerateImportFooterCode(std::ostream&)
 }
 
 void cmExportInstallAndroidMKGenerator::GenerateImportTargetCode(
-  std::ostream& os, const cmGeneratorTarget* target)
+  std::ostream& os, cmGeneratorTarget const* target,
+  cmStateEnums::TargetType /*targetType*/)
 {
   std::string targetName = this->Namespace;
   targetName += target->GetExportName();

+ 4 - 2
Source/cmExportInstallAndroidMKGenerator.h

@@ -12,6 +12,7 @@
 
 #include "cmExportFileGenerator.h"
 #include "cmExportInstallFileGenerator.h"
+#include "cmStateTypes.h"
 
 class cmGeneratorTarget;
 class cmInstallExportGenerator;
@@ -41,8 +42,9 @@ protected:
   void GenerateImportHeaderCode(std::ostream& os,
                                 const std::string& config = "") override;
   void GenerateImportFooterCode(std::ostream& os) override;
-  void GenerateImportTargetCode(std::ostream& os,
-                                const cmGeneratorTarget* target) override;
+  void GenerateImportTargetCode(
+    std::ostream& os, cmGeneratorTarget const* target,
+    cmStateEnums::TargetType /*targetType*/) override;
   void GenerateExpectedTargetsCode(
     std::ostream& os, const std::string& expectedTargets) override;
   void GenerateImportPropertyCode(

+ 18 - 4
Source/cmExportInstallFileGenerator.cxx

@@ -75,11 +75,12 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
   // Create all the imported targets.
   for (cmTargetExport* te : allTargets) {
     cmGeneratorTarget* gt = te->Target;
+    cmStateEnums::TargetType targetType = this->GetExportTargetType(te);
 
     requiresConfigFiles =
-      requiresConfigFiles || gt->GetType() != cmStateEnums::INTERFACE_LIBRARY;
+      requiresConfigFiles || targetType != cmStateEnums::INTERFACE_LIBRARY;
 
-    this->GenerateImportTargetCode(os, gt);
+    this->GenerateImportTargetCode(os, gt, targetType);
 
     ImportPropertyMap properties;
 
@@ -114,7 +115,7 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
         require2_8_12 = true;
       }
     }
-    if (gt->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+    if (targetType == cmStateEnums::INTERFACE_LIBRARY) {
       require3_0_0 = true;
     }
     if (gt->GetProperty("INTERFACE_SOURCES")) {
@@ -308,7 +309,7 @@ void cmExportInstallFileGenerator::GenerateImportTargetsConfig(
   // Add each target in the set to the export.
   for (cmTargetExport* te : *this->IEGen->GetExportSet()->GetTargetExports()) {
     // Collect import properties for this target.
-    if (te->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+    if (this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY) {
       continue;
     }
 
@@ -426,6 +427,19 @@ void cmExportInstallFileGenerator::SetImportLocationProperty(
   }
 }
 
+cmStateEnums::TargetType cmExportInstallFileGenerator::GetExportTargetType(
+  cmTargetExport const* targetExport) const
+{
+  cmStateEnums::TargetType targetType = targetExport->Target->GetType();
+  // An OBJECT library installed with no OBJECTS DESTINATION
+  // is transformed to an INTERFACE library.
+  if (targetType == cmStateEnums::OBJECT_LIBRARY &&
+      targetExport->ObjectsGenerator == nullptr) {
+    targetType = cmStateEnums::INTERFACE_LIBRARY;
+  }
+  return targetType;
+}
+
 void cmExportInstallFileGenerator::HandleMissingTarget(
   std::string& link_libs, std::vector<std::string>& missingTargets,
   cmGeneratorTarget* depender, cmGeneratorTarget* dependee)

+ 4 - 0
Source/cmExportInstallFileGenerator.h

@@ -6,6 +6,7 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include "cmExportFileGenerator.h"
+#include "cmStateTypes.h"
 
 #include <iosfwd>
 #include <map>
@@ -17,6 +18,7 @@ class cmGeneratorTarget;
 class cmGlobalGenerator;
 class cmInstallExportGenerator;
 class cmInstallTargetGenerator;
+class cmTargetExport;
 
 /** \class cmExportInstallFileGenerator
  * \brief Generate a file exporting targets from an install tree.
@@ -57,6 +59,8 @@ protected:
   void GenerateImportTargetsConfig(
     std::ostream& os, const std::string& config, std::string const& suffix,
     std::vector<std::string>& missingTargets) override;
+  cmStateEnums::TargetType GetExportTargetType(
+    cmTargetExport const* targetExport) const;
   void HandleMissingTarget(std::string& link_libs,
                            std::vector<std::string>& missingTargets,
                            cmGeneratorTarget* depender,

+ 1 - 1
Source/cmExportTryCompileFileGenerator.cxx

@@ -32,7 +32,7 @@ bool cmExportTryCompileFileGenerator::GenerateMainFile(std::ostream& os)
     this->Exports.pop_back();
     if (emitted.insert(te).second) {
       emittedDeps.insert(te);
-      this->GenerateImportTargetCode(os, te);
+      this->GenerateImportTargetCode(os, te, te->GetType());
 
       ImportPropertyMap properties;
 

+ 14 - 17
Source/cmInstallCommand.cxx

@@ -360,17 +360,6 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
         this->SetError(e.str());
         return false;
       }
-      if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
-        std::string reason;
-        if (!this->Makefile->GetGlobalGenerator()->HasKnownObjectFileLocation(
-              &reason)) {
-          std::ostringstream e;
-          e << "TARGETS given OBJECT library \"" << tgt
-            << "\" which may not be installed" << reason << ".";
-          this->SetError(e.str());
-          return false;
-        }
-      }
       // Store the target in the list to be installed.
       targets.push_back(target);
     } else {
@@ -534,15 +523,23 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
       case cmStateEnums::OBJECT_LIBRARY: {
         // Objects use OBJECT properties.
         if (!objectArgs.GetDestination().empty()) {
+          // Verify that we know where the objects are to install them.
+          std::string reason;
+          if (!this->Makefile->GetGlobalGenerator()
+                 ->HasKnownObjectFileLocation(&reason)) {
+            std::ostringstream e;
+            e << "TARGETS given OBJECT library \"" << target.GetName()
+              << "\" whose objects may not be installed" << reason << ".";
+            this->SetError(e.str());
+            return false;
+          }
+
           objectGenerator =
             CreateInstallTargetGenerator(target, objectArgs, false);
         } else {
-          std::ostringstream e;
-          e << "TARGETS given no OBJECTS DESTINATION for object library "
-               "target \""
-            << target.GetName() << "\".";
-          this->SetError(e.str());
-          return false;
+          // Installing an OBJECT library without a destination transforms
+          // it to an INTERFACE library.  It installs no files but can be
+          // exported.
         }
       } break;
       case cmStateEnums::EXECUTABLE: {

+ 13 - 5
Tests/ExportImport/Export/CMakeLists.txt

@@ -83,11 +83,13 @@ set_property(TARGET testLib7 PROPERTY OUTPUT_NAME testLib7-$<CONFIG>)
 add_library(testLib8 OBJECT testLib8A.c testLib8B.c sub/testLib8C.c)
 
 if(NOT CMAKE_GENERATOR STREQUAL "Xcode" OR NOT CMAKE_OSX_ARCHITECTURES MATCHES "[;$]")
-  set(maybe_testLib8 testLib8)
+  set(maybe_OBJECTS_DESTINATION OBJECTS DESTINATION $<1:lib>)
 else()
-  set(maybe_testLib8 "")
+  set(maybe_OBJECTS_DESTINATION "")
 endif()
 
+add_library(testLib9Obj OBJECT testLib9Obj.c)
+
 # Test using the target_link_libraries command to set the
 # LINK_INTERFACE_LIBRARIES* properties.  We construct two libraries
 # providing the same two symbols.  In each library one of the symbols
@@ -483,7 +485,7 @@ install(
   TARGETS
   testExe1 testLib1 testLib2 testExe2 testLib3 testLib4 testExe3 testExe4
   testExe2lib testLib4lib testLib4libdbg testLib4libopt
-  testLib6 testLib7 ${maybe_testLib8}
+  testLib6 testLib7 testLib8
   testLibCycleA testLibCycleB
   testLibNoSONAME
   cmp0022NEW cmp0022OLD
@@ -492,10 +494,15 @@ install(
   RUNTIME DESTINATION $<1:bin>
   LIBRARY DESTINATION $<1:lib> NAMELINK_SKIP
   ARCHIVE DESTINATION $<1:lib>
-  OBJECTS DESTINATION $<1:lib>
+  ${maybe_OBJECTS_DESTINATION}
   FRAMEWORK DESTINATION Frameworks
   BUNDLE DESTINATION Applications
   )
+install(
+  TARGETS
+  testLib9Obj
+  EXPORT exp
+  )
 if (APPLE)
   file(COPY testLib4.h DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/testLib4.framework/Headers)
   file(COPY testLib4.h DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Debug/testLib4.framework/Headers)
@@ -545,7 +552,8 @@ export(TARGETS testExe1 testLib1 testLib2 testLib3
   FILE ExportBuildTree.cmake
   )
 export(TARGETS testExe2 testLib4 testLib5 testLib6 testLib7 testExe3 testExe4 testExe2lib
-  ${maybe_testLib8}
+  testLib8
+  testLib9Obj
   testLib4lib testLib4libdbg testLib4libopt
   testLibCycleA testLibCycleB
   testLibNoSONAME

+ 4 - 0
Tests/ExportImport/Export/testLib9Obj.c

@@ -0,0 +1,4 @@
+int testLib9Obj(void)
+{
+  return 0;
+}

+ 14 - 0
Tests/ExportImport/Import/A/CMakeLists.txt

@@ -229,6 +229,8 @@ add_library(imp_lib1b STATIC imp_lib1.c)
 target_link_libraries(imp_lib1b bld_testLib2)
 
 if(NOT CMAKE_GENERATOR STREQUAL "Xcode" OR NOT CMAKE_OSX_ARCHITECTURES MATCHES "[;$]")
+  set(bld_objlib_type OBJECT_LIBRARY)
+
   # Create a executable that is using objects imported from the install tree
   add_executable(imp_testLib8 imp_testLib8.c $<TARGET_OBJECTS:exp_testLib8>)
 
@@ -236,6 +238,18 @@ if(NOT CMAKE_GENERATOR STREQUAL "Xcode" OR NOT CMAKE_OSX_ARCHITECTURES MATCHES "
     # Create a executable that is using objects imported from the build tree
     add_executable(imp_testLib8b imp_testLib8.c $<TARGET_OBJECTS:bld_testLib8>)
   endif()
+else()
+  set(bld_objlib_type INTERFACE_LIBRARY)
+endif()
+
+# Check that object libraries were transformed on export as expected.
+get_property(type TARGET exp_testLib9Obj PROPERTY TYPE)
+if(NOT type STREQUAL INTERFACE_LIBRARY)
+  message(FATAL_ERROR "exp_testLib9Obj type is '${type}', not 'INTERFACE_LIBRARY'")
+endif()
+get_property(type TARGET bld_testLib9Obj PROPERTY TYPE)
+if(NOT type STREQUAL "${bld_objlib_type}")
+  message(FATAL_ERROR "bld_testLib9Obj type is '${type}', not '${bld_objlib_type}'")
 endif()
 
 #-----------------------------------------------------------------------------

+ 0 - 1
Tests/RunCMake/ObjectLibrary/ExportNotSupported-result.txt

@@ -1 +0,0 @@
-1

+ 0 - 5
Tests/RunCMake/ObjectLibrary/ExportNotSupported-stderr.txt

@@ -1,5 +0,0 @@
-CMake Error at ExportNotSupported.cmake:[0-9]+ \(export\):
-  export given OBJECT library "A" which may not be exported under Xcode with
-  multiple architectures.
-Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)

+ 0 - 2
Tests/RunCMake/ObjectLibrary/ExportNotSupported.cmake

@@ -1,2 +0,0 @@
-add_library(A OBJECT a.c)
-export(TARGETS A FILE AExport.cmake)

+ 2 - 2
Tests/RunCMake/ObjectLibrary/InstallNotSupported-stderr.txt

@@ -1,5 +1,5 @@
 CMake Error at InstallNotSupported.cmake:[0-9]+ \(install\):
-  install TARGETS given OBJECT library "A" which may not be installed under
-  Xcode with multiple architectures.
+  install TARGETS given OBJECT library "A" whose objects may not be installed
+  under Xcode with multiple architectures.
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)

+ 1 - 2
Tests/RunCMake/ObjectLibrary/RunCMakeTest.cmake

@@ -6,14 +6,13 @@ run_cmake(BadSourceExpression3)
 run_cmake(BadObjSource1)
 run_cmake(BadObjSource2)
 if(RunCMake_GENERATOR STREQUAL "Xcode" AND "$ENV{CMAKE_OSX_ARCHITECTURES}" MATCHES "[;$]")
-  run_cmake(ExportNotSupported)
   run_cmake(ImportNotSupported)
   run_cmake(InstallNotSupported)
 else()
-  run_cmake(Export)
   run_cmake(Import)
   run_cmake(Install)
 endif()
+run_cmake(Export)
 run_cmake(LinkObjLHS)
 run_cmake(LinkObjRHS1)
 run_cmake(LinkObjRHS2)