From 650e61f833c2cc3ace8405a22eb80a10cfc6187c Mon Sep 17 00:00:00 2001
From: Stephen Kelly <steveire@gmail.com>
Date: Sat, 5 Jan 2013 12:13:49 +0100
Subject: [PATCH] Add a convenient way to add the includes install dir to the
 INTERFACE.

Export the INCLUDES DESTINATION without appending to the
INTERFACE_INCLUDE_DIRECTORIES of the target itself. That way, a target
can be exported multiple times with different INCLUDES DESTINATION
without unintended cross-pollution of export sets.
---
 Source/cmExportFileGenerator.cxx              | 12 ++++---
 Source/cmExportFileGenerator.h                |  4 ++-
 Source/cmExportInstallFileGenerator.cxx       | 12 +++----
 Source/cmInstallCommand.cxx                   | 24 +++++++++++++
 Source/cmInstallCommand.h                     |  5 +++
 Source/cmInstallCommandArguments.cxx          | 34 +++++++++++++++++++
 Source/cmInstallCommandArguments.h            | 13 +++++++
 Source/cmTargetExport.h                       |  3 ++
 Tests/ExportImport/Export/CMakeLists.txt      | 19 ++++++++++-
 Tests/ExportImport/Import/A/deps_iface.c      |  3 ++
 .../include_directories/RunCMakeTest.cmake    |  1 +
 .../TargetInterfaceIncludes-result.txt        |  1 +
 .../TargetInterfaceIncludes-stderr.txt        |  4 +++
 .../TargetInterfaceIncludes.cmake             |  4 +++
 14 files changed, 127 insertions(+), 12 deletions(-)
 create mode 100644 Tests/RunCMake/include_directories/TargetInterfaceIncludes-result.txt
 create mode 100644 Tests/RunCMake/include_directories/TargetInterfaceIncludes-stderr.txt
 create mode 100644 Tests/RunCMake/include_directories/TargetInterfaceIncludes.cmake

diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx
index b4e6e81240..cff539f117 100644
--- a/Source/cmExportFileGenerator.cxx
+++ b/Source/cmExportFileGenerator.cxx
@@ -277,27 +277,31 @@ static bool checkInterfaceDirs(const std::string &prepro,
 
 //----------------------------------------------------------------------------
 void cmExportFileGenerator::PopulateIncludeDirectoriesInterface(
-                      cmTarget *target,
+                      cmTargetExport *tei,
                       cmGeneratorExpression::PreprocessContext preprocessRule,
                       ImportPropertyMap &properties,
                       std::vector<std::string> &missingTargets)
 {
+  cmTarget *target = tei->Target;
   assert(preprocessRule == cmGeneratorExpression::InstallInterface);
 
   const char *propName = "INTERFACE_INCLUDE_DIRECTORIES";
   const char *input = target->GetProperty(propName);
-  if (!input)
+  if (!input && tei->InterfaceIncludeDirectories.empty())
     {
     return;
     }
-  if (!*input)
+  if (!*input && tei->InterfaceIncludeDirectories.empty())
     {
     // Set to empty
     properties[propName] = "";
     return;
     }
 
-  std::string prepro = cmGeneratorExpression::Preprocess(input,
+  std::string includes = (input?input:"");
+  const char* sep = input ? ";" : "";
+  includes += sep + tei->InterfaceIncludeDirectories;
+  std::string prepro = cmGeneratorExpression::Preprocess(includes,
                                                           preprocessRule);
   if (!prepro.empty())
     {
diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h
index a624ba6c44..9628b96817 100644
--- a/Source/cmExportFileGenerator.h
+++ b/Source/cmExportFileGenerator.h
@@ -15,6 +15,8 @@
 #include "cmCommand.h"
 #include "cmGeneratorExpression.h"
 
+class cmTargetExport;
+
 /** \class cmExportFileGenerator
  * \brief Generate a file exporting targets from a build or install tree.
  *
@@ -114,7 +116,7 @@ protected:
   void GenerateInterfaceProperties(cmTarget *target, std::ostream& os,
                                    const ImportPropertyMap &properties);
   void PopulateIncludeDirectoriesInterface(
-                      cmTarget *target,
+                      cmTargetExport *target,
                       cmGeneratorExpression::PreprocessContext preprocessRule,
                       ImportPropertyMap &properties,
                       std::vector<std::string> &missingTargets);
diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx
index ce7afc5a99..c97d4ff7c9 100644
--- a/Source/cmExportInstallFileGenerator.cxx
+++ b/Source/cmExportInstallFileGenerator.cxx
@@ -39,7 +39,7 @@ std::string cmExportInstallFileGenerator::GetConfigImportFileGlob()
 //----------------------------------------------------------------------------
 bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
 {
-  std::vector<cmTarget*> allTargets;
+  std::vector<cmTargetExport*> allTargets;
   {
   std::string expectedTargets;
   std::string sep;
@@ -49,10 +49,10 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
     {
     expectedTargets += sep + this->Namespace + (*tei)->Target->GetExportName();
     sep = " ";
-    cmTargetExport const* te = *tei;
+    cmTargetExport * te = *tei;
     if(this->ExportedTargets.insert(te->Target).second)
       {
-      allTargets.push_back(te->Target);
+      allTargets.push_back(te);
       }
     else
       {
@@ -115,16 +115,16 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
 
   bool require2_8_12 = false;
   // Create all the imported targets.
-  for(std::vector<cmTarget*>::const_iterator
+  for(std::vector<cmTargetExport*>::const_iterator
         tei = allTargets.begin();
       tei != allTargets.end(); ++tei)
     {
-    cmTarget* te = *tei;
+    cmTarget* te = (*tei)->Target;
     this->GenerateImportTargetCode(os, te);
 
     ImportPropertyMap properties;
 
-    this->PopulateIncludeDirectoriesInterface(te,
+    this->PopulateIncludeDirectoriesInterface(*tei,
                                   cmGeneratorExpression::InstallInterface,
                                   properties, missingTargets);
     this->PopulateInterfaceProperty("INTERFACE_SYSTEM_INCLUDE_DIRECTORIES",
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index d3a8037243..4649eda149 100644
--- a/Source/cmInstallCommand.cxx
+++ b/Source/cmInstallCommand.cxx
@@ -219,6 +219,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
   cmCAStringVector runtimeArgVector      (&argHelper,"RUNTIME",&group);
   cmCAStringVector frameworkArgVector    (&argHelper,"FRAMEWORK",&group);
   cmCAStringVector bundleArgVector       (&argHelper,"BUNDLE",&group);
+  cmCAStringVector includesArgVector     (&argHelper,"INCLUDES",&group);
   cmCAStringVector privateHeaderArgVector(&argHelper,"PRIVATE_HEADER",&group);
   cmCAStringVector publicHeaderArgVector (&argHelper,"PUBLIC_HEADER",&group);
   cmCAStringVector resourceArgVector     (&argHelper,"RESOURCE",&group);
@@ -247,6 +248,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
   cmInstallCommandArguments privateHeaderArgs(this->DefaultComponentName);
   cmInstallCommandArguments publicHeaderArgs(this->DefaultComponentName);
   cmInstallCommandArguments resourceArgs(this->DefaultComponentName);
+  cmInstallCommandIncludesArgument includesArgs;
 
   // now parse the args for specific parts of the target (e.g. LIBRARY,
   // RUNTIME, ARCHIVE etc.
@@ -258,6 +260,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
   privateHeaderArgs.Parse(&privateHeaderArgVector.GetVector(), &unknownArgs);
   publicHeaderArgs.Parse (&publicHeaderArgVector.GetVector(),  &unknownArgs);
   resourceArgs.Parse     (&resourceArgVector.GetVector(),      &unknownArgs);
+  includesArgs.Parse     (&includesArgVector.GetVector(),      &unknownArgs);
 
   if(!unknownArgs.empty())
     {
@@ -292,6 +295,13 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
     return false;
     }
 
+  if(!includesArgs.GetIncludeDirs().empty() && exports.GetString().empty())
+    {
+    this->SetError("TARGETS given INCLUDES DESTINATION, but EXPORT set "
+      "not specified.");
+    return false;
+    }
+
   // Enforce argument rules too complex to specify for the
   // general-purpose parser.
   if(archiveArgs.GetNamelinkOnly() ||
@@ -747,6 +757,20 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
       te->RuntimeGenerator = runtimeGenerator;
       this->Makefile->GetLocalGenerator()->GetGlobalGenerator()
         ->GetExportSets()[exports.GetString()]->AddTargetExport(te);
+
+      std::vector<std::string> dirs = includesArgs.GetIncludeDirs();
+      if(!dirs.empty())
+        {
+        std::string dirString;
+        const char *sep = "";
+        for (std::vector<std::string>::const_iterator it = dirs.begin();
+            it != dirs.end(); ++it)
+          {
+          te->InterfaceIncludeDirectories += sep;
+          te->InterfaceIncludeDirectories += *it;
+          sep = ";";
+          }
+        }
       }
     }
 
diff --git a/Source/cmInstallCommand.h b/Source/cmInstallCommand.h
index 39acd235e1..65095011f4 100644
--- a/Source/cmInstallCommand.h
+++ b/Source/cmInstallCommand.h
@@ -102,6 +102,7 @@ public:
       "          [[ARCHIVE|LIBRARY|RUNTIME|FRAMEWORK|BUNDLE|\n"
       "            PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]\n"
       "           [DESTINATION <dir>]\n"
+      "           [INCLUDES DESTINATION [<dir> ...]]\n"
       "           [PERMISSIONS permissions...]\n"
       "           [CONFIGURATIONS [Debug|Release|...]]\n"
       "           [COMPONENT <component>]\n"
@@ -130,6 +131,10 @@ public:
       "all target types.  If only one is given then only targets of that "
       "type will be installed (which can be used to install just a DLL or "
       "just an import library)."
+      "The INCLUDES DESTINATION specifies a list of directories which will "
+      "be added to the INTERFACE_INCLUDE_DIRECTORIES of the <targets> when "
+      "exported by install(EXPORT).  If a relative path is specified, it is "
+      "treated as relative to the $<INSTALL_PREFIX>."
       "\n"
       "The PRIVATE_HEADER, PUBLIC_HEADER, and RESOURCE arguments cause "
       "subsequent properties to be applied to installing a FRAMEWORK "
diff --git a/Source/cmInstallCommandArguments.cxx b/Source/cmInstallCommandArguments.cxx
index 8e48f08e6c..91ea861a43 100644
--- a/Source/cmInstallCommandArguments.cxx
+++ b/Source/cmInstallCommandArguments.cxx
@@ -203,3 +203,37 @@ bool cmInstallCommandArguments::CheckPermissions(
   // This is not a valid permission.
   return false;
 }
+
+cmInstallCommandIncludesArgument::cmInstallCommandIncludesArgument()
+{
+
+}
+
+const std::vector<std::string>&
+cmInstallCommandIncludesArgument::GetIncludeDirs() const
+{
+  return this->IncludeDirs;
+}
+
+void cmInstallCommandIncludesArgument::Parse(
+                                        const std::vector<std::string>* args,
+                                        std::vector<std::string>*)
+{
+  if(args->empty())
+    {
+    return;
+    }
+  std::vector<std::string>::const_iterator it = args->begin();
+  ++it;
+  for ( ; it != args->end(); ++it)
+    {
+    std::string dir = *it;
+    if (!cmSystemTools::FileIsFullPath(it->c_str())
+        && cmGeneratorExpression::Find(*it) == std::string::npos)
+      {
+      dir = "$<INSTALL_PREFIX>/" + dir;
+      }
+    cmSystemTools::ConvertToUnixSlashes(dir);
+    this->IncludeDirs.push_back(dir);
+    }
+}
diff --git a/Source/cmInstallCommandArguments.h b/Source/cmInstallCommandArguments.h
index 01f7d56797..90347e6686 100644
--- a/Source/cmInstallCommandArguments.h
+++ b/Source/cmInstallCommandArguments.h
@@ -65,4 +65,17 @@ class cmInstallCommandArguments
     bool CheckPermissions();
 };
 
+class cmInstallCommandIncludesArgument
+{
+  public:
+    cmInstallCommandIncludesArgument();
+    void Parse(const std::vector<std::string>* args,
+               std::vector<std::string>* unconsumedArgs);
+
+    const std::vector<std::string>& GetIncludeDirs() const;
+
+  private:
+    std::vector<std::string> IncludeDirs;
+};
+
 #endif
diff --git a/Source/cmTargetExport.h b/Source/cmTargetExport.h
index c9d87fb7e1..76658887d4 100644
--- a/Source/cmTargetExport.h
+++ b/Source/cmTargetExport.h
@@ -12,6 +12,8 @@
 #ifndef cmTargetExport_h
 #define cmTargetExport_h
 
+#include "cmStandardIncludes.h"
+
 class cmTarget;
 class cmInstallTargetGenerator;
 class cmInstallFilesGenerator;
@@ -33,6 +35,7 @@ public:
   cmInstallTargetGenerator* FrameworkGenerator;
   cmInstallTargetGenerator* BundleGenerator;
   cmInstallFilesGenerator* HeaderGenerator;
+  std::string InterfaceIncludeDirectories;
   ///@}
 };
 
diff --git a/Tests/ExportImport/Export/CMakeLists.txt b/Tests/ExportImport/Export/CMakeLists.txt
index 370dffecfe..737ec432b9 100644
--- a/Tests/ExportImport/Export/CMakeLists.txt
+++ b/Tests/ExportImport/Export/CMakeLists.txt
@@ -270,9 +270,26 @@ install(TARGETS testLibRequired
                 testLibIncludeRequired5
                 testLibIncludeRequired6
                 testSharedLibRequired
-        EXPORT RequiredExp DESTINATION lib )
+        EXPORT RequiredExp DESTINATION lib
+        INCLUDES DESTINATION
+          installIncludesTest
+          $<INSTALL_PREFIX>/installIncludesTest2)
 install(EXPORT RequiredExp NAMESPACE Req:: FILE testLibRequiredTargets.cmake DESTINATION lib/cmake/testLibRequired)
 
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/installIncludesTest")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/installIncludesTest/installIncludesTest.h" "// No content\n")
+
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/installIncludesTest2")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/installIncludesTest2/installIncludesTest2.h" "// No content\n")
+install(FILES
+  "${CMAKE_CURRENT_BINARY_DIR}/installIncludesTest/installIncludesTest.h"
+  DESTINATION installIncludesTest
+)
+install(FILES
+  "${CMAKE_CURRENT_BINARY_DIR}/installIncludesTest2/installIncludesTest2.h"
+  DESTINATION installIncludesTest2
+)
+
 install(TARGETS testLibDepends testSharedLibDepends EXPORT DependsExp DESTINATION lib )
 install(EXPORT DependsExp FILE testLibDependsTargets.cmake DESTINATION lib/cmake/testLibDepends)
 
diff --git a/Tests/ExportImport/Import/A/deps_iface.c b/Tests/ExportImport/Import/A/deps_iface.c
index e73ca26bf1..d9dc3319c6 100644
--- a/Tests/ExportImport/Import/A/deps_iface.c
+++ b/Tests/ExportImport/Import/A/deps_iface.c
@@ -3,6 +3,9 @@
 #include "testLibIncludeRequired2.h"
 #include "testLibIncludeRequired6.h"
 
+#include "installIncludesTest.h"
+#include "installIncludesTest2.h"
+
 #ifndef testLibRequired_IFACE_DEFINE
 #error Expected testLibRequired_IFACE_DEFINE
 #endif
diff --git a/Tests/RunCMake/include_directories/RunCMakeTest.cmake b/Tests/RunCMake/include_directories/RunCMakeTest.cmake
index 520dd448d0..e582960fb9 100644
--- a/Tests/RunCMake/include_directories/RunCMakeTest.cmake
+++ b/Tests/RunCMake/include_directories/RunCMakeTest.cmake
@@ -9,3 +9,4 @@ run_cmake(RelativePathInInterface)
 run_cmake(ImportedTarget)
 run_cmake(RelativePathInGenex)
 run_cmake(CMP0021)
+run_cmake(TargetInterfaceIncludes)
diff --git a/Tests/RunCMake/include_directories/TargetInterfaceIncludes-result.txt b/Tests/RunCMake/include_directories/TargetInterfaceIncludes-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/include_directories/TargetInterfaceIncludes-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/include_directories/TargetInterfaceIncludes-stderr.txt b/Tests/RunCMake/include_directories/TargetInterfaceIncludes-stderr.txt
new file mode 100644
index 0000000000..d1539275d6
--- /dev/null
+++ b/Tests/RunCMake/include_directories/TargetInterfaceIncludes-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at TargetInterfaceIncludes.cmake:3 \(install\):
+  install TARGETS given INCLUDES DESTINATION, but EXPORT set not specified.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/include_directories/TargetInterfaceIncludes.cmake b/Tests/RunCMake/include_directories/TargetInterfaceIncludes.cmake
new file mode 100644
index 0000000000..92f31fc642
--- /dev/null
+++ b/Tests/RunCMake/include_directories/TargetInterfaceIncludes.cmake
@@ -0,0 +1,4 @@
+
+add_library(empty empty.cpp)
+install(TARGETS empty DESTINATION lib INCLUDES DESTINATION include)
+# install(EXPORT )