Browse Source

LINK_DIRECTORIES: Add new properties and commands

These new capabilities enable to manage link directories

Two new properties:
* target properties: LINK_DIRECTORIES and INTERFACE_LINK_DIRECTORIES

One new command
* target_link_directories(): to populate target properties

Fixes: #17215
Marc Chevrier 7 years ago
parent
commit
a71caab46b
41 changed files with 662 additions and 62 deletions
  1. 35 9
      Help/command/link_directories.rst
  2. 55 0
      Help/command/target_link_directories.rst
  3. 1 0
      Help/manual/cmake-commands.7.rst
  4. 2 0
      Help/manual/cmake-properties.7.rst
  5. 12 3
      Help/prop_dir/LINK_DIRECTORIES.rst
  6. 9 0
      Help/prop_tgt/INTERFACE_LINK_DIRECTORIES.rst
  7. 18 0
      Help/prop_tgt/LINK_DIRECTORIES.rst
  8. 9 0
      Help/release/dev/LINK_DIRECTORIES.rst
  9. 2 0
      Source/CMakeLists.txt
  10. 1 1
      Source/cmCPluginAPI.cxx
  11. 3 0
      Source/cmCommands.cxx
  12. 5 6
      Source/cmComputeLinkInformation.cxx
  13. 3 0
      Source/cmExportBuildFileGenerator.cxx
  14. 31 0
      Source/cmExportFileGenerator.cxx
  15. 4 0
      Source/cmExportFileGenerator.h
  16. 2 0
      Source/cmExportInstallFileGenerator.cxx
  17. 1 0
      Source/cmGeneratorExpressionDAGChecker.h
  18. 102 5
      Source/cmGeneratorTarget.cxx
  19. 6 2
      Source/cmGeneratorTarget.h
  20. 5 2
      Source/cmLinkDirectoriesCommand.cxx
  21. 16 15
      Source/cmMakefile.cxx
  22. 3 0
      Source/cmMakefile.h
  23. 5 0
      Source/cmState.cxx
  24. 53 0
      Source/cmStateDirectory.cxx
  25. 8 0
      Source/cmStateDirectory.h
  26. 4 0
      Source/cmStatePrivate.h
  27. 7 0
      Source/cmStateSnapshot.cxx
  28. 66 13
      Source/cmTarget.cxx
  29. 5 6
      Source/cmTarget.h
  30. 61 0
      Source/cmTargetLinkDirectoriesCommand.cxx
  31. 41 0
      Source/cmTargetLinkDirectoriesCommand.h
  32. 40 0
      Tests/CMakeCommands/target_link_directories/CMakeLists.txt
  33. 7 0
      Tests/CMakeCommands/target_link_directories/LinkDirectoriesLib.c
  34. 2 0
      Tests/CMakeCommands/target_link_directories/subdir/CMakeLists.txt
  35. 1 0
      Tests/CMakeLists.txt
  36. 12 0
      Tests/ExportImport/Export/CMakeLists.txt
  37. 5 0
      Tests/ExportImport/Import/A/CMakeLists.txt
  38. 14 0
      Tests/LinkDirectory/External/CMakeLists.txt
  39. 2 0
      Tests/RunCMake/set_property/LINK_DIRECTORIES-stdout.txt
  40. 3 0
      Tests/RunCMake/set_property/LINK_DIRECTORIES.cmake
  41. 1 0
      Tests/RunCMake/set_property/RunCMakeTest.cmake

+ 35 - 9
Help/command/link_directories.rst

@@ -1,19 +1,45 @@
 link_directories
 ----------------
 
-Specify directories in which the linker will look for libraries.
+Add directories in which the linker will look for libraries.
 
 ::
 
-  link_directories(directory1 directory2 ...)
+  link_directories(directory1 [directory2 ...])
 
-Specify the paths in which the linker should search for libraries.
-The command will apply only to targets created after it is called.
+Add the paths in which the linker should search for libraries.
 Relative paths given to this command are interpreted as relative to
 the current source directory, see :policy:`CMP0015`.
 
-Note that this command is rarely necessary.  Library locations
-returned by :command:`find_package` and :command:`find_library` are
-absolute paths. Pass these absolute library file paths directly to the
-:command:`target_link_libraries` command.  CMake will ensure the linker finds
-them.
+The directories are added to the :prop_dir:`LINK_DIRECTORIES` directory
+property for the current ``CMakeLists.txt`` file, converting relative
+paths to absolute as needed.
+The command will apply only to targets created after it is called.
+
+Arguments to ``link_directories`` may use "generator expressions" with
+the syntax "$<...>".  See the :manual:`cmake-generator-expressions(7)`
+manual for available expressions.  See the :manual:`cmake-buildsystem(7)`
+manual for more on defining buildsystem properties.
+
+.. note::
+
+  This command is rarely necessary and should be avoided where there are
+  other choices.  Prefer to pass full absolute paths to libraries where
+  possible, since this ensures the correct library will always be linked.
+  The :command:`find_library` command provides the full path, which can
+  generally be used directly in calls to :command:`target_link_libraries`.
+  Situations where a library search path may be needed include:
+
+  - Project generators like Xcode where the user can switch target
+    architecture at build time, but a full path to a library cannot
+    be used because it only provides one architecture (i.e. it is not
+    a universal binary).
+  - Libraries may themselves have other private library dependencies
+    that expect to be found via ``RPATH`` mechanisms, but some linkers
+    are not able to fully decode those paths (e.g. due to the presence
+    of things like ``$ORIGIN``).
+
+  If a library search path must be provided, prefer to localize the effect
+  where possible by using the :command:`target_link_directories` command
+  rather than ``link_directories()``.  The target-specific command can also
+  control how the search directories propagate to other dependent targets.

+ 55 - 0
Help/command/target_link_directories.rst

@@ -0,0 +1,55 @@
+target_link_directories
+-----------------------
+
+Add link directories to a target.
+
+::
+
+  target_link_directories(<target> [BEFORE]
+    <INTERFACE|PUBLIC|PRIVATE> [items1...]
+    [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
+
+Specify the paths in which the linker should search for libraries when
+linking a given target.  Each item can be an absolute or relative path,
+with the latter being interpreted as relative to the current source
+directory.  These items will be added to the link command.
+
+The named ``<target>`` must have been created by a command such as
+:command:`add_executable` or :command:`add_library` and must not be an
+:ref:`ALIAS target <Alias Targets>`.
+
+The ``INTERFACE``, ``PUBLIC`` and ``PRIVATE`` keywords are required to
+specify the scope of the items that follow them.  ``PRIVATE`` and
+``PUBLIC`` items will populate the :prop_tgt:`LINK_DIRECTORIES` property
+of ``<target>``.  ``PUBLIC`` and ``INTERFACE`` items will populate the
+:prop_tgt:`INTERFACE_LINK_DIRECTORIES` property of ``<target>``
+(:ref:`IMPORTED targets <Imported Targets>` only support ``INTERFACE`` items).
+Each item specifies a link directory and will be converted to an absolute
+path if necessary before adding it to the relevant property.  Repeated
+calls for the same ``<target>`` append items in the order called.
+
+If ``BEFORE`` is specified, the content will be prepended to the relevant
+property instead of being appended.
+
+Arguments to ``target_link_directories`` may use "generator expressions"
+with the syntax ``$<...>``. See the :manual:`cmake-generator-expressions(7)`
+manual for available expressions.  See the :manual:`cmake-buildsystem(7)`
+manual for more on defining buildsystem properties.
+
+.. note::
+
+  This command is rarely necessary and should be avoided where there are
+  other choices.  Prefer to pass full absolute paths to libraries where
+  possible, since this ensures the correct library will always be linked.
+  The :command:`find_library` command provides the full path, which can
+  generally be used directly in calls to :command:`target_link_libraries`.
+  Situations where a library search path may be needed include:
+
+  - Project generators like Xcode where the user can switch target
+    architecture at build time, but a full path to a library cannot
+    be used because it only provides one architecture (i.e. it is not
+    a universal binary).
+  - Libraries may themselves have other private library dependencies
+    that expect to be found via ``RPATH`` mechanisms, but some linkers
+    are not able to fully decode those paths (e.g. due to the presence
+    of things like ``$ORIGIN``).

+ 1 - 0
Help/manual/cmake-commands.7.rst

@@ -111,6 +111,7 @@ These commands are available only in CMake projects.
    /command/target_compile_features
    /command/target_compile_options
    /command/target_include_directories
+   /command/target_link_directories
    /command/target_link_libraries
    /command/target_link_options
    /command/target_sources

+ 2 - 0
Help/manual/cmake-properties.7.rst

@@ -228,6 +228,7 @@ Properties on Targets
    /prop_tgt/INTERFACE_COMPILE_OPTIONS
    /prop_tgt/INTERFACE_INCLUDE_DIRECTORIES
    /prop_tgt/INTERFACE_LINK_DEPENDS
+   /prop_tgt/INTERFACE_LINK_DIRECTORIES
    /prop_tgt/INTERFACE_LINK_LIBRARIES
    /prop_tgt/INTERFACE_LINK_OPTIONS
    /prop_tgt/INTERFACE_POSITION_INDEPENDENT_CODE
@@ -252,6 +253,7 @@ Properties on Targets
    /prop_tgt/LINK_DEPENDS_NO_SHARED
    /prop_tgt/LINK_DEPENDS
    /prop_tgt/LINKER_LANGUAGE
+   /prop_tgt/LINK_DIRECTORIES
    /prop_tgt/LINK_FLAGS_CONFIG
    /prop_tgt/LINK_FLAGS
    /prop_tgt/LINK_INTERFACE_LIBRARIES_CONFIG

+ 12 - 3
Help/prop_dir/LINK_DIRECTORIES.rst

@@ -3,6 +3,15 @@ LINK_DIRECTORIES
 
 List of linker search directories.
 
-This read-only property specifies the list of directories given so far
-to the link_directories command.  It is intended for debugging
-purposes.
+This property holds a :ref:`;-list <CMake Language Lists>` of directories
+and is typically populated using the :command:`link_directories` command.
+It gets its initial value from its parent directory, if it has one.
+
+The directory property is used to initialize the :prop_tgt:`LINK_DIRECTORIES`
+target property when a target is created.  That target property is used
+by the generators to set the library search directories for the linker.
+
+Contents of ``LINK_DIRECTORIES`` may use "generator expressions" with
+the syntax ``$<...>``.  See the :manual:`cmake-generator-expressions(7)`
+manual for available expressions.  See the :manual:`cmake-buildsystem(7)`
+manual for more on defining buildsystem properties.

+ 9 - 0
Help/prop_tgt/INTERFACE_LINK_DIRECTORIES.rst

@@ -0,0 +1,9 @@
+INTERFACE_LINK_DIRECTORIES
+--------------------------
+
+.. |property_name| replace:: link directories
+.. |command_name| replace:: :command:`target_link_directories`
+.. |PROPERTY_INTERFACE_NAME| replace:: ``INTERFACE_LINK_DIRECTORIES``
+.. |PROPERTY_LINK| replace:: :prop_tgt:`LINK_DIRECTORIES`
+.. |PROPERTY_GENEX| replace:: ``$<TARGET_PROPERTY:foo,INTERFACE_LINK_DIRECTORIES>``
+.. include:: INTERFACE_BUILD_PROPERTY.txt

+ 18 - 0
Help/prop_tgt/LINK_DIRECTORIES.rst

@@ -0,0 +1,18 @@
+LINK_DIRECTORIES
+----------------
+
+List of directories to use for the link step of shared library, module
+and executable targets.
+
+This property holds a :ref:`;-list <CMake Language Lists>` of directories
+specified so far for its target.  Use the :command:`target_link_directories`
+command to append more search directories.
+
+This property is initialized by the :prop_dir:`LINK_DIRECTORIES` directory
+property when a target is created, and is used by the generators to set
+the search directories for the linker.
+
+Contents of ``LINK_DIRECTORIES`` may use "generator expressions" with the
+syntax ``$<...>``.  See the :manual:`cmake-generator-expressions(7)` manual
+for available expressions.  See the :manual:`cmake-buildsystem(7)` manual
+for more on defining buildsystem properties.

+ 9 - 0
Help/release/dev/LINK_DIRECTORIES.rst

@@ -0,0 +1,9 @@
+LINK_DIRECTORIES
+----------------
+
+* CMake gained new capabilities to manage link directories:
+
+  * :prop_tgt:`LINK_DIRECTORIES` and :prop_tgt:`INTERFACE_LINK_DIRECTORIES`
+    target properties.
+  * :command:`target_link_directories` command to add link directories to
+    targets.

+ 2 - 0
Source/CMakeLists.txt

@@ -579,6 +579,8 @@ set(SRCS
   cmTargetIncludeDirectoriesCommand.h
   cmTargetLinkOptionsCommand.cxx
   cmTargetLinkOptionsCommand.h
+  cmTargetLinkDirectoriesCommand.cxx
+  cmTargetLinkDirectoriesCommand.h
   cmTargetLinkLibrariesCommand.cxx
   cmTargetLinkLibrariesCommand.h
   cmTargetPropCommandBase.cxx

+ 1 - 1
Source/cmCPluginAPI.cxx

@@ -171,7 +171,7 @@ void CCONV cmAddLinkDirectoryForTarget(void* arg, const char* tgt,
       " for directory ", d);
     return;
   }
-  t->AddLinkDirectory(d);
+  t->InsertLinkDirectory(d, mf->GetBacktrace());
 }
 
 void CCONV cmAddExecutable(void* arg, const char* exename, int numSrcs,

+ 3 - 0
Source/cmCommands.cxx

@@ -101,6 +101,7 @@
 #  include "cmRemoveDefinitionsCommand.h"
 #  include "cmSourceGroupCommand.h"
 #  include "cmSubdirDependsCommand.h"
+#  include "cmTargetLinkDirectoriesCommand.h"
 #  include "cmTargetLinkOptionsCommand.h"
 #  include "cmUseMangledMesaCommand.h"
 #  include "cmUtilitySourceCommand.h"
@@ -278,6 +279,8 @@ void GetProjectCommands(cmState* state)
   state->AddBuiltinCommand("link_libraries", new cmLinkLibrariesCommand);
   state->AddBuiltinCommand("target_link_options",
                            new cmTargetLinkOptionsCommand);
+  state->AddBuiltinCommand("target_link_directories",
+                           new cmTargetLinkDirectoriesCommand);
   state->AddBuiltinCommand("load_cache", new cmLoadCacheCommand);
   state->AddBuiltinCommand("qt_wrap_cpp", new cmQTWrapCPPCommand);
   state->AddBuiltinCommand("qt_wrap_ui", new cmQTWrapUICommand);

+ 5 - 6
Source/cmComputeLinkInformation.cxx

@@ -357,10 +357,10 @@ cmComputeLinkInformation::cmComputeLinkInformation(
   }
 
   // Add the search path entries requested by the user to path ordering.
-  this->OrderLinkerSearchPath->AddUserDirectories(
-    this->Target->GetLinkDirectories());
-  this->OrderRuntimeSearchPath->AddUserDirectories(
-    this->Target->GetLinkDirectories());
+  std::vector<std::string> directories;
+  this->Target->GetLinkDirectories(directories, config, this->LinkLanguage);
+  this->OrderLinkerSearchPath->AddUserDirectories(directories);
+  this->OrderRuntimeSearchPath->AddUserDirectories(directories);
 
   // Set up the implicit link directories.
   this->LoadImplicitLinkInfo();
@@ -387,8 +387,7 @@ cmComputeLinkInformation::cmComputeLinkInformation(
   if (this->OldLinkDirMode) {
     // Construct a mask to not bother with this behavior for link
     // directories already specified by the user.
-    std::vector<std::string> const& dirs = this->Target->GetLinkDirectories();
-    this->OldLinkDirMask.insert(dirs.begin(), dirs.end());
+    this->OldLinkDirMask.insert(directories.begin(), directories.end());
   }
 
   this->CMP0060Warn = this->Makefile->PolicyOptionalWarningEnabled(

+ 3 - 0
Source/cmExportBuildFileGenerator.cxx

@@ -98,6 +98,9 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
     this->PopulateInterfaceProperty("INTERFACE_LINK_OPTIONS", gte,
                                     cmGeneratorExpression::BuildInterface,
                                     properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_LINK_DIRECTORIES", gte,
+                                    cmGeneratorExpression::BuildInterface,
+                                    properties, missingTargets);
     this->PopulateInterfaceProperty("INTERFACE_LINK_DEPENDS", gte,
                                     cmGeneratorExpression::BuildInterface,
                                     properties, missingTargets);

+ 31 - 0
Source/cmExportFileGenerator.cxx

@@ -451,6 +451,37 @@ void cmExportFileGenerator::PopulateLinkDependsInterface(
   }
 }
 
+void cmExportFileGenerator::PopulateLinkDirectoriesInterface(
+  cmTargetExport* tei, cmGeneratorExpression::PreprocessContext preprocessRule,
+  ImportPropertyMap& properties, std::vector<std::string>& missingTargets)
+{
+  cmGeneratorTarget* gt = tei->Target;
+  assert(preprocessRule == cmGeneratorExpression::InstallInterface);
+
+  const char* propName = "INTERFACE_LINK_DIRECTORIES";
+  const char* input = gt->GetProperty(propName);
+
+  if (!input) {
+    return;
+  }
+
+  if (!*input) {
+    properties[propName].clear();
+    return;
+  }
+
+  std::string prepro =
+    cmGeneratorExpression::Preprocess(input, preprocessRule, true);
+  if (!prepro.empty()) {
+    this->ResolveTargetsInGeneratorExpressions(prepro, gt, missingTargets);
+
+    if (!checkInterfaceDirs(prepro, gt, propName)) {
+      return;
+    }
+    properties[propName] = prepro;
+  }
+}
+
 void cmExportFileGenerator::PopulateInterfaceProperty(
   const std::string& propName, cmGeneratorTarget* target,
   cmGeneratorExpression::PreprocessContext preprocessRule,

+ 4 - 0
Source/cmExportFileGenerator.h

@@ -147,6 +147,10 @@ protected:
     cmTargetExport* target,
     cmGeneratorExpression::PreprocessContext preprocessRule,
     ImportPropertyMap& properties, std::vector<std::string>& missingTargets);
+  void PopulateLinkDirectoriesInterface(
+    cmTargetExport* target,
+    cmGeneratorExpression::PreprocessContext preprocessRule,
+    ImportPropertyMap& properties, std::vector<std::string>& missingTargets);
   void PopulateLinkDependsInterface(
     cmTargetExport* target,
     cmGeneratorExpression::PreprocessContext preprocessRule,

+ 2 - 0
Source/cmExportInstallFileGenerator.cxx

@@ -106,6 +106,8 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
     this->PopulateInterfaceProperty("INTERFACE_LINK_OPTIONS", gt,
                                     cmGeneratorExpression::InstallInterface,
                                     properties, missingTargets);
+    this->PopulateLinkDirectoriesInterface(
+      te, cmGeneratorExpression::InstallInterface, properties, missingTargets);
     this->PopulateLinkDependsInterface(
       te, cmGeneratorExpression::InstallInterface, properties, missingTargets);
 

+ 1 - 0
Source/cmGeneratorExpressionDAGChecker.h

@@ -28,6 +28,7 @@ class cmGeneratorTarget;
   SELECT(F, EvaluatingSources, SOURCES)                                       \
   SELECT(F, EvaluatingCompileFeatures, COMPILE_FEATURES)                      \
   SELECT(F, EvaluatingLinkOptions, LINK_OPTIONS)                              \
+  SELECT(F, EvaluatingLinkDirectories, LINK_DIRECTORIES)                      \
   SELECT(F, EvaluatingLinkDepends, LINK_DEPENDS)
 
 #define CM_FOR_EACH_TRANSITIVE_PROPERTY(F)                                    \

+ 102 - 5
Source/cmGeneratorTarget.cxx

@@ -103,6 +103,7 @@ cmGeneratorTarget::cmGeneratorTarget(cmTarget* t, cmLocalGenerator* lg)
   , DebugCompileFeaturesDone(false)
   , DebugCompileDefinitionsDone(false)
   , DebugLinkOptionsDone(false)
+  , DebugLinkDirectoriesDone(false)
   , DebugSourcesDone(false)
   , LinkImplementationLanguageIsContextDependent(true)
   , UtilityItemsDone(false)
@@ -133,6 +134,10 @@ cmGeneratorTarget::cmGeneratorTarget(cmTarget* t, cmLocalGenerator* lg)
                                      t->GetLinkOptionsBacktraces(),
                                      this->LinkOptionsEntries);
 
+  CreatePropertyGeneratorExpressions(t->GetLinkDirectoriesEntries(),
+                                     t->GetLinkDirectoriesBacktraces(),
+                                     this->LinkDirectoriesEntries);
+
   CreatePropertyGeneratorExpressions(t->GetSourceEntries(),
                                      t->GetSourceBacktraces(),
                                      this->SourceEntries, true);
@@ -150,6 +155,7 @@ cmGeneratorTarget::~cmGeneratorTarget()
   cmDeleteAll(this->CompileFeaturesEntries);
   cmDeleteAll(this->CompileDefinitionsEntries);
   cmDeleteAll(this->LinkOptionsEntries);
+  cmDeleteAll(this->LinkDirectoriesEntries);
   cmDeleteAll(this->SourceEntries);
   cmDeleteAll(this->LinkInformation);
 }
@@ -1704,11 +1710,6 @@ cmListFileBacktrace cmGeneratorTarget::GetBacktrace() const
   return this->Target->GetBacktrace();
 }
 
-const std::vector<std::string>& cmGeneratorTarget::GetLinkDirectories() const
-{
-  return this->Target->GetLinkDirectories();
-}
-
 const std::set<std::string>& cmGeneratorTarget::GetUtilities() const
 {
   return this->Target->GetUtilities();
@@ -3067,6 +3068,102 @@ void cmGeneratorTarget::GetStaticLibraryLinkOptions(
   cmDeleteAll(entries);
 }
 
+namespace {
+void processLinkDirectories(
+  cmGeneratorTarget const* tgt,
+  const std::vector<cmGeneratorTarget::TargetPropertyEntry*>& entries,
+  std::vector<std::string>& directories,
+  std::unordered_set<std::string>& uniqueDirectories,
+  cmGeneratorExpressionDAGChecker* dagChecker, const std::string& config,
+  bool debugDirectories, std::string const& language)
+{
+  for (cmGeneratorTarget::TargetPropertyEntry* entry : entries) {
+    cmLinkImplItem const& item = entry->LinkImplItem;
+    std::string const& targetName = item.AsStr();
+
+    std::vector<std::string> entryDirectories;
+    cmSystemTools::ExpandListArgument(
+      entry->ge->Evaluate(tgt->GetLocalGenerator(), config, false, tgt,
+                          dagChecker, language),
+      entryDirectories);
+
+    std::string usedDirectories;
+    for (std::string& entryDirectory : entryDirectories) {
+      if (!cmSystemTools::FileIsFullPath(entryDirectory)) {
+        std::ostringstream e;
+        if (!targetName.empty()) {
+          /* clang-format off */
+          e << "Target \"" << targetName << "\" contains relative "
+            "path in its INTERFACE_LINK_DIRECTORIES:\n"
+            "  \"" << entryDirectory << "\"";
+          /* clang-format on */
+          tgt->GetLocalGenerator()->IssueMessage(cmake::FATAL_ERROR, e.str());
+          return;
+        }
+      }
+
+      // Sanitize the path the same way the link_directories command does
+      // in case projects set the LINK_DIRECTORIES property directly.
+      cmSystemTools::ConvertToUnixSlashes(entryDirectory);
+      if (uniqueDirectories.insert(entryDirectory).second) {
+        directories.push_back(entryDirectory);
+        if (debugDirectories) {
+          usedDirectories += " * " + entryDirectory + "\n";
+        }
+      }
+    }
+    if (!usedDirectories.empty()) {
+      tgt->GetLocalGenerator()->GetCMakeInstance()->IssueMessage(
+        cmake::LOG,
+        std::string("Used link directories for target ") + tgt->GetName() +
+          ":\n" + usedDirectories,
+        entry->ge->GetBacktrace());
+    }
+  }
+}
+}
+
+void cmGeneratorTarget::GetLinkDirectories(std::vector<std::string>& result,
+                                           const std::string& config,
+                                           const std::string& language) const
+{
+  std::unordered_set<std::string> uniqueDirectories;
+
+  cmGeneratorExpressionDAGChecker dagChecker(this, "LINK_DIRECTORIES", nullptr,
+                                             nullptr);
+
+  std::vector<std::string> debugProperties;
+  const char* debugProp =
+    this->Makefile->GetDefinition("CMAKE_DEBUG_TARGET_PROPERTIES");
+  if (debugProp) {
+    cmSystemTools::ExpandListArgument(debugProp, debugProperties);
+  }
+
+  bool debugDirectories = !this->DebugLinkDirectoriesDone &&
+    std::find(debugProperties.begin(), debugProperties.end(),
+              "LINK_DIRECTORIES") != debugProperties.end();
+
+  if (this->GlobalGenerator->GetConfigureDoneCMP0026()) {
+    this->DebugLinkDirectoriesDone = true;
+  }
+
+  processLinkDirectories(this, this->LinkDirectoriesEntries, result,
+                         uniqueDirectories, &dagChecker, config,
+                         debugDirectories, language);
+
+  std::vector<cmGeneratorTarget::TargetPropertyEntry*>
+    linkInterfaceLinkDirectoriesEntries;
+
+  AddInterfaceEntries(this, config, "INTERFACE_LINK_DIRECTORIES",
+                      linkInterfaceLinkDirectoriesEntries);
+
+  processLinkDirectories(this, linkInterfaceLinkDirectoriesEntries, result,
+                         uniqueDirectories, &dagChecker, config,
+                         debugDirectories, language);
+
+  cmDeleteAll(linkInterfaceLinkDirectoriesEntries);
+}
+
 namespace {
 void processLinkDepends(
   cmGeneratorTarget const* tgt,

+ 6 - 2
Source/cmGeneratorTarget.h

@@ -273,8 +273,6 @@ public:
 
   cmListFileBacktrace GetBacktrace() const;
 
-  const std::vector<std::string>& GetLinkDirectories() const;
-
   std::set<std::string> const& GetUtilities() const;
   cmListFileBacktrace const* GetUtilityBacktrace(const std::string& u) const;
 
@@ -435,6 +433,10 @@ public:
                                    const std::string& config,
                                    const std::string& language) const;
 
+  void GetLinkDirectories(std::vector<std::string>& result,
+                          const std::string& config,
+                          const std::string& language) const;
+
   void GetLinkDepends(std::vector<std::string>& result,
                       const std::string& config,
                       const std::string& language) const;
@@ -825,6 +827,7 @@ private:
   std::vector<TargetPropertyEntry*> CompileFeaturesEntries;
   std::vector<TargetPropertyEntry*> CompileDefinitionsEntries;
   std::vector<TargetPropertyEntry*> LinkOptionsEntries;
+  std::vector<TargetPropertyEntry*> LinkDirectoriesEntries;
   std::vector<TargetPropertyEntry*> SourceEntries;
   mutable std::set<std::string> LinkImplicitNullProperties;
 
@@ -874,6 +877,7 @@ private:
   mutable bool DebugCompileFeaturesDone;
   mutable bool DebugCompileDefinitionsDone;
   mutable bool DebugLinkOptionsDone;
+  mutable bool DebugLinkDirectoriesDone;
   mutable bool DebugSourcesDone;
   mutable bool LinkImplementationLanguageIsContextDependent;
   mutable bool UtilityItemsDone;

+ 5 - 2
Source/cmLinkDirectoriesCommand.cxx

@@ -4,6 +4,7 @@
 
 #include <sstream>
 
+#include "cmGeneratorExpression.h"
 #include "cmMakefile.h"
 #include "cmPolicies.h"
 #include "cmSystemTools.h"
@@ -29,7 +30,8 @@ void cmLinkDirectoriesCommand::AddLinkDir(std::string const& dir)
 {
   std::string unixPath = dir;
   cmSystemTools::ConvertToUnixSlashes(unixPath);
-  if (!cmSystemTools::FileIsFullPath(unixPath)) {
+  if (!cmSystemTools::FileIsFullPath(unixPath) &&
+      !cmGeneratorExpression::StartsWithGeneratorExpression(unixPath)) {
     bool convertToAbsolute = false;
     std::ostringstream e;
     /* clang-format off */
@@ -41,6 +43,7 @@ void cmLinkDirectoriesCommand::AddLinkDir(std::string const& dir)
       case cmPolicies::WARN:
         e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0015);
         this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, e.str());
+        break;
       case cmPolicies::OLD:
         // OLD behavior does not convert
         break;
@@ -61,5 +64,5 @@ void cmLinkDirectoriesCommand::AddLinkDir(std::string const& dir)
       unixPath = tmp;
     }
   }
-  this->Makefile->AppendProperty("LINK_DIRECTORIES", unixPath.c_str());
+  this->Makefile->AddLinkDirectory(unixPath);
 }

+ 16 - 15
Source/cmMakefile.cxx

@@ -243,6 +243,17 @@ cmBacktraceRange cmMakefile::GetLinkOptionsBacktraces() const
   return this->StateSnapshot.GetDirectory().GetLinkOptionsEntryBacktraces();
 }
 
+cmStringRange cmMakefile::GetLinkDirectoriesEntries() const
+{
+  return this->StateSnapshot.GetDirectory().GetLinkDirectoriesEntries();
+}
+
+cmBacktraceRange cmMakefile::GetLinkDirectoriesBacktraces() const
+{
+  return this->StateSnapshot.GetDirectory()
+    .GetLinkDirectoriesEntryBacktraces();
+}
+
 cmListFileBacktrace cmMakefile::GetBacktrace() const
 {
   return this->Backtrace;
@@ -1237,6 +1248,11 @@ void cmMakefile::AddLinkOption(std::string const& option)
   this->AppendProperty("LINK_OPTIONS", option.c_str());
 }
 
+void cmMakefile::AddLinkDirectory(std::string const& directory)
+{
+  this->AppendProperty("LINK_DIRECTORIES", directory.c_str());
+}
+
 bool cmMakefile::ParseDefineFlag(std::string const& def, bool remove)
 {
   // Create a regular expression to match valid definitions.
@@ -1335,10 +1351,6 @@ void cmMakefile::InitializeFromParent(cmMakefile* parent)
   // link libraries
   this->SetProperty("LINK_LIBRARIES", parent->GetProperty("LINK_LIBRARIES"));
 
-  // link directories
-  this->SetProperty("LINK_DIRECTORIES",
-                    parent->GetProperty("LINK_DIRECTORIES"));
-
   // the initial project name
   this->StateSnapshot.SetProjectName(parent->StateSnapshot.GetProjectName());
 
@@ -1872,17 +1884,6 @@ void cmMakefile::AddGlobalLinkInformation(cmTarget& target)
       return;
     default:;
   }
-  if (const char* linkDirsProp = this->GetProperty("LINK_DIRECTORIES")) {
-    std::vector<std::string> linkDirs;
-    cmSystemTools::ExpandListArgument(linkDirsProp, linkDirs);
-
-    for (std::string& linkDir : linkDirs) {
-      // Sanitize the path the same way the link_directories command does
-      // in case projects set the LINK_DIRECTORIES property directly.
-      cmSystemTools::ConvertToUnixSlashes(linkDir);
-      target.AddLinkDirectory(linkDir);
-    }
-  }
 
   if (const char* linkLibsProp = this->GetProperty("LINK_LIBRARIES")) {
     std::vector<std::string> linkLibs;

+ 3 - 0
Source/cmMakefile.h

@@ -182,6 +182,7 @@ public:
   void AddCompileDefinition(std::string const& definition);
   void AddCompileOption(std::string const& option);
   void AddLinkOption(std::string const& option);
+  void AddLinkDirectory(std::string const& directory);
 
   /** Create a new imported target with the name and type given.  */
   cmTarget* AddImportedTarget(const std::string& name,
@@ -802,6 +803,8 @@ public:
   cmBacktraceRange GetCompileDefinitionsBacktraces() const;
   cmStringRange GetLinkOptionsEntries() const;
   cmBacktraceRange GetLinkOptionsBacktraces() const;
+  cmStringRange GetLinkDirectoriesEntries() const;
+  cmBacktraceRange GetLinkDirectoriesBacktraces() const;
 
   std::set<std::string> const& GetSystemIncludeDirectories() const
   {

+ 5 - 0
Source/cmState.cxx

@@ -284,6 +284,8 @@ cmStateSnapshot cmState::Reset()
     it->CompileOptionsBacktraces.clear();
     it->LinkOptions.clear();
     it->LinkOptionsBacktraces.clear();
+    it->LinkDirectories.clear();
+    it->LinkDirectoriesBacktraces.clear();
     it->DirectoryEnd = pos;
     it->NormalTargetNames.clear();
     it->Properties.clear();
@@ -660,6 +662,7 @@ cmStateSnapshot cmState::CreateBaseSnapshot()
   pos->CompileDefinitionsPosition = 0;
   pos->CompileOptionsPosition = 0;
   pos->LinkOptionsPosition = 0;
+  pos->LinkDirectoriesPosition = 0;
   pos->BuildSystemDirectory->DirectoryEnd = pos;
   pos->Policies = this->PolicyStack.Root();
   pos->PolicyRoot = this->PolicyStack.Root();
@@ -813,6 +816,8 @@ cmStateSnapshot cmState::Pop(cmStateSnapshot const& originSnapshot)
     prevPos->BuildSystemDirectory->CompileOptions.size();
   prevPos->LinkOptionsPosition =
     prevPos->BuildSystemDirectory->LinkOptions.size();
+  prevPos->LinkDirectoriesPosition =
+    prevPos->BuildSystemDirectory->LinkDirectories.size();
   prevPos->BuildSystemDirectory->DirectoryEnd = prevPos;
 
   if (!pos->Keep && this->SnapshotData.IsLast(pos)) {

+ 53 - 0
Source/cmStateDirectory.cxx

@@ -396,6 +396,43 @@ void cmStateDirectory::ClearLinkOptions()
                this->Snapshot_.Position->LinkOptionsPosition);
 }
 
+cmStringRange cmStateDirectory::GetLinkDirectoriesEntries() const
+{
+  return GetPropertyContent(this->DirectoryState->LinkDirectories,
+                            this->Snapshot_.Position->LinkDirectoriesPosition);
+}
+
+cmBacktraceRange cmStateDirectory::GetLinkDirectoriesEntryBacktraces() const
+{
+  return GetPropertyBacktraces(
+    this->DirectoryState->LinkDirectories,
+    this->DirectoryState->LinkDirectoriesBacktraces,
+    this->Snapshot_.Position->LinkDirectoriesPosition);
+}
+
+void cmStateDirectory::AppendLinkDirectoriesEntry(
+  const std::string& vec, const cmListFileBacktrace& lfbt)
+{
+  AppendEntry(this->DirectoryState->LinkDirectories,
+              this->DirectoryState->LinkDirectoriesBacktraces,
+              this->Snapshot_.Position->LinkDirectoriesPosition, vec, lfbt);
+}
+
+void cmStateDirectory::SetLinkDirectories(const std::string& vec,
+                                          const cmListFileBacktrace& lfbt)
+{
+  SetContent(this->DirectoryState->LinkDirectories,
+             this->DirectoryState->LinkDirectoriesBacktraces,
+             this->Snapshot_.Position->LinkDirectoriesPosition, vec, lfbt);
+}
+
+void cmStateDirectory::ClearLinkDirectories()
+{
+  ClearContent(this->DirectoryState->LinkDirectories,
+               this->DirectoryState->LinkDirectoriesBacktraces,
+               this->Snapshot_.Position->LinkDirectoriesPosition);
+}
+
 void cmStateDirectory::SetProperty(const std::string& prop, const char* value,
                                    cmListFileBacktrace const& lfbt)
 {
@@ -431,6 +468,14 @@ void cmStateDirectory::SetProperty(const std::string& prop, const char* value,
     this->SetLinkOptions(value, lfbt);
     return;
   }
+  if (prop == "LINK_DIRECTORIES") {
+    if (!value) {
+      this->ClearLinkDirectories();
+      return;
+    }
+    this->SetLinkDirectories(value, lfbt);
+    return;
+  }
 
   this->DirectoryState->Properties.SetProperty(prop, value);
 }
@@ -455,6 +500,10 @@ void cmStateDirectory::AppendProperty(const std::string& prop,
     this->AppendLinkOptionsEntry(value, lfbt);
     return;
   }
+  if (prop == "LINK_DIRECTORIES") {
+    this->AppendLinkDirectoriesEntry(value, lfbt);
+    return;
+  }
 
   this->DirectoryState->Properties.AppendProperty(prop, value, asString);
 }
@@ -542,6 +591,10 @@ const char* cmStateDirectory::GetProperty(const std::string& prop,
     output = cmJoin(this->GetLinkOptionsEntries(), ";");
     return output.c_str();
   }
+  if (prop == "LINK_DIRECTORIES") {
+    output = cmJoin(this->GetLinkDirectoriesEntries(), ";");
+    return output.c_str();
+  }
 
   const char* retVal = this->DirectoryState->Properties.GetPropertyValue(prop);
   if (!retVal && chain) {

+ 8 - 0
Source/cmStateDirectory.h

@@ -65,6 +65,14 @@ public:
   void SetLinkOptions(std::string const& vec, cmListFileBacktrace const& lfbt);
   void ClearLinkOptions();
 
+  cmStringRange GetLinkDirectoriesEntries() const;
+  cmBacktraceRange GetLinkDirectoriesEntryBacktraces() const;
+  void AppendLinkDirectoriesEntry(std::string const& vec,
+                                  cmListFileBacktrace const& lfbt);
+  void SetLinkDirectories(std::string const& vec,
+                          cmListFileBacktrace const& lfbt);
+  void ClearLinkDirectories();
+
   void SetProperty(const std::string& prop, const char* value,
                    cmListFileBacktrace const& lfbt);
   void AppendProperty(const std::string& prop, const char* value,

+ 4 - 0
Source/cmStatePrivate.h

@@ -43,6 +43,7 @@ struct cmStateDetail::SnapshotDataType
   std::vector<std::string>::size_type CompileDefinitionsPosition;
   std::vector<std::string>::size_type CompileOptionsPosition;
   std::vector<std::string>::size_type LinkOptionsPosition;
+  std::vector<std::string>::size_type LinkDirectoriesPosition;
 };
 
 struct cmStateDetail::PolicyStackEntry : public cmPolicies::PolicyMap
@@ -88,6 +89,9 @@ struct cmStateDetail::BuildsystemDirectoryStateType
   std::vector<std::string> LinkOptions;
   std::vector<cmListFileBacktrace> LinkOptionsBacktraces;
 
+  std::vector<std::string> LinkDirectories;
+  std::vector<cmListFileBacktrace> LinkDirectoriesBacktraces;
+
   std::vector<std::string> NormalTargetNames;
 
   std::string ProjectName;

+ 7 - 0
Source/cmStateSnapshot.cxx

@@ -398,6 +398,13 @@ void cmStateSnapshot::InitializeFromParent()
     this->Position->BuildSystemDirectory->LinkOptionsBacktraces,
     this->Position->LinkOptionsPosition);
 
+  InitializeContentFromParent(
+    parent->BuildSystemDirectory->LinkDirectories,
+    this->Position->BuildSystemDirectory->LinkDirectories,
+    parent->BuildSystemDirectory->LinkDirectoriesBacktraces,
+    this->Position->BuildSystemDirectory->LinkDirectoriesBacktraces,
+    this->Position->LinkDirectoriesPosition);
+
   const char* include_regex =
     parent->BuildSystemDirectory->Properties.GetPropertyValue(
       "INCLUDE_REGULAR_EXPRESSION");

+ 66 - 13
Source/cmTarget.cxx

@@ -168,6 +168,8 @@ public:
   std::vector<cmListFileBacktrace> SourceBacktraces;
   std::vector<std::string> LinkOptionsEntries;
   std::vector<cmListFileBacktrace> LinkOptionsBacktraces;
+  std::vector<std::string> LinkDirectoriesEntries;
+  std::vector<cmListFileBacktrace> LinkDirectoriesBacktraces;
   std::vector<std::string> LinkImplementationPropertyEntries;
   std::vector<cmListFileBacktrace> LinkImplementationPropertyBacktraces;
 };
@@ -391,6 +393,18 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     this->Internal->LinkOptionsBacktraces.insert(
       this->Internal->LinkOptionsBacktraces.end(),
       parentLinkOptionsBts.begin(), parentLinkOptionsBts.end());
+
+    const cmStringRange parentLinkDirectories =
+      this->Makefile->GetLinkDirectoriesEntries();
+    const cmBacktraceRange parentLinkDirectoriesBts =
+      this->Makefile->GetLinkDirectoriesBacktraces();
+
+    this->Internal->LinkDirectoriesEntries.insert(
+      this->Internal->LinkDirectoriesEntries.end(),
+      parentLinkDirectories.begin(), parentLinkDirectories.end());
+    this->Internal->LinkDirectoriesBacktraces.insert(
+      this->Internal->LinkDirectoriesBacktraces.end(),
+      parentLinkDirectoriesBts.begin(), parentLinkDirectoriesBts.end());
   }
 
   if (this->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
@@ -654,19 +668,6 @@ cmSourceFile* cmTarget::AddSource(const std::string& src)
                                            cmSourceFileLocationKind::Known);
 }
 
-void cmTarget::AddLinkDirectory(const std::string& d)
-{
-  // Make sure we don't add unnecessary search directories.
-  if (this->LinkDirectoriesEmmitted.insert(d).second) {
-    this->LinkDirectories.push_back(d);
-  }
-}
-
-const std::vector<std::string>& cmTarget::GetLinkDirectories() const
-{
-  return this->LinkDirectories;
-}
-
 void cmTarget::ClearDependencyInformation(cmMakefile& mf)
 {
   std::string depname = this->GetName();
@@ -874,6 +875,16 @@ cmBacktraceRange cmTarget::GetLinkOptionsBacktraces() const
   return cmMakeRange(this->Internal->LinkOptionsBacktraces);
 }
 
+cmStringRange cmTarget::GetLinkDirectoriesEntries() const
+{
+  return cmMakeRange(this->Internal->LinkDirectoriesEntries);
+}
+
+cmBacktraceRange cmTarget::GetLinkDirectoriesBacktraces() const
+{
+  return cmMakeRange(this->Internal->LinkDirectoriesBacktraces);
+}
+
 cmStringRange cmTarget::GetLinkImplementationEntries() const
 {
   return cmMakeRange(this->Internal->LinkImplementationPropertyEntries);
@@ -900,6 +911,7 @@ void cmTarget::SetProperty(const std::string& prop, const char* value)
   MAKE_STATIC_PROP(IMPORTED_GLOBAL);
   MAKE_STATIC_PROP(INCLUDE_DIRECTORIES);
   MAKE_STATIC_PROP(LINK_OPTIONS);
+  MAKE_STATIC_PROP(LINK_DIRECTORIES);
   MAKE_STATIC_PROP(LINK_LIBRARIES);
   MAKE_STATIC_PROP(MANUALLY_ADDED_DEPENDENCIES);
   MAKE_STATIC_PROP(NAME);
@@ -986,6 +998,14 @@ void cmTarget::SetProperty(const std::string& prop, const char* value)
       cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
       this->Internal->LinkOptionsBacktraces.push_back(lfbt);
     }
+  } else if (prop == propLINK_DIRECTORIES) {
+    this->Internal->LinkDirectoriesEntries.clear();
+    this->Internal->LinkDirectoriesBacktraces.clear();
+    if (value) {
+      this->Internal->LinkDirectoriesEntries.push_back(value);
+      cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
+      this->Internal->LinkDirectoriesBacktraces.push_back(lfbt);
+    }
   } else if (prop == propLINK_LIBRARIES) {
     this->Internal->LinkImplementationPropertyEntries.clear();
     this->Internal->LinkImplementationPropertyBacktraces.clear();
@@ -1097,6 +1117,12 @@ void cmTarget::AppendProperty(const std::string& prop, const char* value,
       cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
       this->Internal->LinkOptionsBacktraces.push_back(lfbt);
     }
+  } else if (prop == "LINK_DIRECTORIES") {
+    if (value && *value) {
+      this->Internal->LinkDirectoriesEntries.push_back(value);
+      cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
+      this->Internal->LinkDirectoriesBacktraces.push_back(lfbt);
+    }
   } else if (prop == "LINK_LIBRARIES") {
     if (value && *value) {
       cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
@@ -1194,6 +1220,21 @@ void cmTarget::InsertLinkOption(std::string const& entry,
   this->Internal->LinkOptionsBacktraces.insert(btPosition, bt);
 }
 
+void cmTarget::InsertLinkDirectory(std::string const& entry,
+                                   cmListFileBacktrace const& bt, bool before)
+{
+  std::vector<std::string>::iterator position = before
+    ? this->Internal->LinkDirectoriesEntries.begin()
+    : this->Internal->LinkDirectoriesEntries.end();
+
+  std::vector<cmListFileBacktrace>::iterator btPosition = before
+    ? this->Internal->LinkDirectoriesBacktraces.begin()
+    : this->Internal->LinkDirectoriesBacktraces.end();
+
+  this->Internal->LinkDirectoriesEntries.insert(position, entry);
+  this->Internal->LinkDirectoriesBacktraces.insert(btPosition, bt);
+}
+
 static void cmTargetCheckLINK_INTERFACE_LIBRARIES(const std::string& prop,
                                                   const char* value,
                                                   cmMakefile* context,
@@ -1314,6 +1355,7 @@ const char* cmTarget::GetProperty(const std::string& prop) const
   MAKE_STATIC_PROP(COMPILE_OPTIONS);
   MAKE_STATIC_PROP(COMPILE_DEFINITIONS);
   MAKE_STATIC_PROP(LINK_OPTIONS);
+  MAKE_STATIC_PROP(LINK_DIRECTORIES);
   MAKE_STATIC_PROP(IMPORTED);
   MAKE_STATIC_PROP(IMPORTED_GLOBAL);
   MAKE_STATIC_PROP(MANUALLY_ADDED_DEPENDENCIES);
@@ -1330,6 +1372,7 @@ const char* cmTarget::GetProperty(const std::string& prop) const
     specialProps.insert(propCOMPILE_OPTIONS);
     specialProps.insert(propCOMPILE_DEFINITIONS);
     specialProps.insert(propLINK_OPTIONS);
+    specialProps.insert(propLINK_DIRECTORIES);
     specialProps.insert(propIMPORTED);
     specialProps.insert(propIMPORTED_GLOBAL);
     specialProps.insert(propMANUALLY_ADDED_DEPENDENCIES);
@@ -1397,6 +1440,16 @@ const char* cmTarget::GetProperty(const std::string& prop) const
       output = cmJoin(this->Internal->LinkOptionsEntries, ";");
       return output.c_str();
     }
+    if (prop == propLINK_DIRECTORIES) {
+      if (this->Internal->LinkDirectoriesEntries.empty()) {
+        return nullptr;
+      }
+
+      static std::string output;
+      output = cmJoin(this->Internal->LinkDirectoriesEntries, ";");
+
+      return output.c_str();
+    }
     if (prop == propMANUALLY_ADDED_DEPENDENCIES) {
       if (this->Utilities.empty()) {
         return nullptr;

+ 5 - 6
Source/cmTarget.h

@@ -154,10 +154,6 @@ public:
                            cmListFileContext const& lfc);
   void GetTllSignatureTraces(std::ostream& s, TLLSignature sig) const;
 
-  const std::vector<std::string>& GetLinkDirectories() const;
-
-  void AddLinkDirectory(const std::string& d);
-
   /**
    * Set the path where this target should be installed. This is relative to
    * INSTALL_PREFIX
@@ -247,6 +243,8 @@ public:
                                cmListFileBacktrace const& bt);
   void InsertLinkOption(std::string const& entry,
                         cmListFileBacktrace const& bt, bool before = false);
+  void InsertLinkDirectory(std::string const& entry,
+                           cmListFileBacktrace const& bt, bool before = false);
 
   void AppendBuildInterfaceIncludes();
 
@@ -277,6 +275,9 @@ public:
   cmStringRange GetLinkOptionsEntries() const;
   cmBacktraceRange GetLinkOptionsBacktraces() const;
 
+  cmStringRange GetLinkDirectoriesEntries() const;
+  cmBacktraceRange GetLinkDirectoriesBacktraces() const;
+
   cmStringRange GetLinkImplementationEntries() const;
   cmBacktraceRange GetLinkImplementationBacktraces() const;
 
@@ -306,14 +307,12 @@ private:
   bool IsGeneratorProvided;
   cmPropertyMap Properties;
   std::set<std::string> SystemIncludeDirectories;
-  std::set<std::string> LinkDirectoriesEmmitted;
   std::set<std::string> Utilities;
   std::map<std::string, cmListFileBacktrace> UtilityBacktraces;
   cmPolicies::PolicyMap PolicyMap;
   std::string Name;
   std::string InstallPath;
   std::string RuntimeInstallPath;
-  std::vector<std::string> LinkDirectories;
   std::vector<cmCustomCommand> PreBuildCommands;
   std::vector<cmCustomCommand> PreLinkCommands;
   std::vector<cmCustomCommand> PostBuildCommands;

+ 61 - 0
Source/cmTargetLinkDirectoriesCommand.cxx

@@ -0,0 +1,61 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmTargetLinkDirectoriesCommand.h"
+
+#include <sstream>
+
+#include "cmAlgorithms.h"
+#include "cmGeneratorExpression.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmake.h"
+
+class cmExecutionStatus;
+
+bool cmTargetLinkDirectoriesCommand::InitialPass(
+  std::vector<std::string> const& args, cmExecutionStatus&)
+{
+  return this->HandleArguments(args, "LINK_DIRECTORIES", PROCESS_BEFORE);
+}
+
+void cmTargetLinkDirectoriesCommand::HandleMissingTarget(
+  const std::string& name)
+{
+  std::ostringstream e;
+  e << "Cannot specify link directories for target \"" << name
+    << "\" which is not built by this project.";
+  this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
+}
+
+std::string cmTargetLinkDirectoriesCommand::Join(
+  const std::vector<std::string>& content)
+{
+  std::vector<std::string> directories;
+
+  for (const auto& dir : content) {
+    auto unixPath = dir;
+    cmSystemTools::ConvertToUnixSlashes(unixPath);
+    if (!cmSystemTools::FileIsFullPath(unixPath) &&
+        !cmGeneratorExpression::StartsWithGeneratorExpression(unixPath)) {
+      auto tmp = this->Makefile->GetCurrentSourceDirectory();
+      tmp += "/";
+      tmp += unixPath;
+      unixPath = tmp;
+    }
+    directories.push_back(unixPath);
+  }
+
+  return cmJoin(directories, ";");
+}
+
+bool cmTargetLinkDirectoriesCommand::HandleDirectContent(
+  cmTarget* tgt, const std::vector<std::string>& content, bool prepend, bool)
+{
+  cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
+
+  tgt->InsertLinkDirectory(this->Join(content), lfbt, prepend);
+
+  return true; // Successfully handled.
+}

+ 41 - 0
Source/cmTargetLinkDirectoriesCommand.h

@@ -0,0 +1,41 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmTargetLinkDirectoriesCommand_h
+#define cmTargetLinkDirectoriesCommand_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
+
+#include "cmTargetPropCommandBase.h"
+
+class cmCommand;
+class cmExecutionStatus;
+class cmTarget;
+
+class cmTargetLinkDirectoriesCommand : public cmTargetPropCommandBase
+{
+public:
+  /**
+   * This is a virtual constructor for the command.
+   */
+  cmCommand* Clone() override { return new cmTargetLinkDirectoriesCommand; }
+
+  /**
+   * This is called when the command is first encountered in
+   * the CMakeLists.txt file.
+   */
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) override;
+
+private:
+  void HandleMissingTarget(const std::string& name) override;
+
+  std::string Join(const std::vector<std::string>& content) override;
+  bool HandleDirectContent(cmTarget* tgt,
+                           const std::vector<std::string>& content,
+                           bool prepend, bool system) override;
+};
+
+#endif

+ 40 - 0
Tests/CMakeCommands/target_link_directories/CMakeLists.txt

@@ -0,0 +1,40 @@
+
+cmake_minimum_required(VERSION 3.12)
+
+project(target_link_directories LANGUAGES C)
+
+add_library(target_link_directories SHARED LinkDirectoriesLib.c)
+# Test no items
+target_link_directories(target_link_directories PRIVATE)
+
+add_library(target_link_directories_2 SHARED EXCLUDE_FROM_ALL LinkDirectoriesLib.c)
+target_link_directories(target_link_directories_2 PRIVATE /private/dir INTERFACE /interface/dir)
+get_target_property(result target_link_directories_2 LINK_DIRECTORIES)
+if (NOT result MATCHES "/private/dir")
+  message(SEND_ERROR "${result} target_link_directories not populated the LINK_DIRECTORIES target property")
+endif()
+get_target_property(result target_link_directories_2 INTERFACE_LINK_DIRECTORIES)
+if (NOT result MATCHES "/interface/dir")
+  message(SEND_ERROR "target_link_directories not populated the INTERFACE_LINK_DIRECTORIES target property of shared library")
+endif()
+
+add_library(target_link_directories_3 STATIC EXCLUDE_FROM_ALL LinkDirectoriesLib.c)
+target_link_directories(target_link_directories_3 INTERFACE /interface/dir)
+get_target_property(result target_link_directories_3 INTERFACE_LINK_DIRECTORIES)
+if (NOT result MATCHES "/interface/dir")
+  message(SEND_ERROR "target_link_directories not populated the INTERFACE_LINK_DIRECTORIES target property of static library")
+endif()
+
+add_library(target_link_directories_4 SHARED EXCLUDE_FROM_ALL LinkDirectoriesLib.c)
+target_link_directories(target_link_directories_4 PRIVATE relative/dir)
+get_target_property(result target_link_directories_4 LINK_DIRECTORIES)
+if (NOT result MATCHES "${CMAKE_CURRENT_SOURCE_DIR}/relative/dir")
+  message(SEND_ERROR "target_link_directories not populated the LINK_DIRECTORIES with relative path")
+endif()
+
+add_subdirectory(subdir)
+target_link_directories(target_link_directories_5 PRIVATE relative/dir)
+get_target_property(result target_link_directories_5 LINK_DIRECTORIES)
+if (NOT result MATCHES "${CMAKE_CURRENT_SOURCE_DIR}/relative/dir")
+  message(SEND_ERROR "target_link_directories not populated the LINK_DIRECTORIES with relative path")
+endif()

+ 7 - 0
Tests/CMakeCommands/target_link_directories/LinkDirectoriesLib.c

@@ -0,0 +1,7 @@
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+  int flags_lib(void)
+{
+  return 0;
+}

+ 2 - 0
Tests/CMakeCommands/target_link_directories/subdir/CMakeLists.txt

@@ -0,0 +1,2 @@
+
+add_library(target_link_directories_5 SHARED EXCLUDE_FROM_ALL ../LinkDirectoriesLib.c)

+ 1 - 0
Tests/CMakeLists.txt

@@ -2823,6 +2823,7 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
   ADD_TEST_MACRO(CMakeCommands.add_compile_definitions add_compile_definitions)
   ADD_TEST_MACRO(CMakeCommands.add_compile_options add_compile_options)
   ADD_TEST_MACRO(CMakeCommands.target_link_libraries target_link_libraries)
+  ADD_TEST_MACRO(CMakeCommands.target_link_directories)
   ADD_TEST_MACRO(CMakeCommands.target_include_directories target_include_directories)
   ADD_TEST_MACRO(CMakeCommands.target_compile_definitions target_compile_definitions)
   ADD_TEST_MACRO(CMakeCommands.target_compile_options target_compile_options)

+ 12 - 0
Tests/ExportImport/Export/CMakeLists.txt

@@ -618,6 +618,18 @@ install(TARGETS testLinkOptions
 export(TARGETS testLinkOptions NAMESPACE bld_ APPEND FILE ExportBuildTree.cmake)
 
 
+#------------------------------------------------------------------------------
+# test export of INTERFACE_LINK_DIRECTORIES
+add_library(testLinkDirectories INTERFACE)
+target_link_directories(testLinkDirectories INTERFACE
+  $<BUILD_INTERFACE:/interface/build>
+  $<INSTALL_INTERFACE:interface/install>)
+
+install(TARGETS testLinkDirectories
+        EXPORT RequiredExp DESTINATION lib)
+export(TARGETS testLinkDirectories NAMESPACE bld_ APPEND FILE ExportBuildTree.cmake)
+
+
 #------------------------------------------------------------------------------
 # test export of INTERFACE_LINK_DEPENDS
 if(CMAKE_GENERATOR MATCHES "Make|Ninja")

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

@@ -489,6 +489,11 @@ endif()
 checkForProperty(bld_testLinkOptions "INTERFACE_LINK_OPTIONS" "INTERFACE_FLAG")
 checkForProperty(Req::testLinkOptions "INTERFACE_LINK_OPTIONS" "INTERFACE_FLAG")
 
+#---------------------------------------------------------------------------------
+# check that imported libraries have the expected INTERFACE_LINK_DIRECTORIES property
+checkForProperty(bld_testLinkDirectories "INTERFACE_LINK_DIRECTORIES" "/interface/build")
+checkForProperty(Req::testLinkDirectories "INTERFACE_LINK_DIRECTORIES" "${CMAKE_INSTALL_PREFIX}/interface/install")
+
 #---------------------------------------------------------------------------------
 # check that imported libraries have the expected INTERFACE_LINK_DEPENDS property
 if(CMAKE_GENERATOR MATCHES "Make|Ninja")

+ 14 - 0
Tests/LinkDirectory/External/CMakeLists.txt

@@ -1,6 +1,20 @@
 cmake_minimum_required(VERSION 2.8)
 project(LinkDirectoryExternal C)
 
+
+add_executable(myexe2 myexe.c)
+set_property(TARGET myexe2 PROPERTY OUTPUT_NAME LinkDirectory2)
+target_link_directories(myexe2 PRIVATE lib "${CMAKE_CURRENT_SOURCE_DIR}/../lib")
+target_link_libraries(myexe2 PRIVATE mylibA mylibB)
+
+add_library (mylibs INTERFACE)
+target_link_directories(mylibs INTERFACE lib "${CMAKE_CURRENT_SOURCE_DIR}/../lib")
+target_link_libraries(mylibs INTERFACE mylibA mylibB)
+add_executable(myexe3 myexe.c)
+set_property(TARGET myexe3 PROPERTY OUTPUT_NAME LinkDirectory3)
+target_link_libraries(myexe3 PRIVATE mylibs)
+
+
 # Test CMP0015 OLD behavior: -L../lib
 cmake_policy(SET CMP0015 OLD)
 link_directories(../lib)

+ 2 - 0
Tests/RunCMake/set_property/LINK_DIRECTORIES-stdout.txt

@@ -0,0 +1,2 @@
+-- Target LINK_DIRECTORIES is 'a;b;c;d;;e'
+-- Directory LINK_DIRECTORIES is 'a;b;c;d;;e'

+ 3 - 0
Tests/RunCMake/set_property/LINK_DIRECTORIES.cmake

@@ -0,0 +1,3 @@
+include(Common.cmake)
+test_target_property(LINK_DIRECTORIES)
+test_directory_property(LINK_DIRECTORIES)

+ 1 - 0
Tests/RunCMake/set_property/RunCMakeTest.cmake

@@ -6,6 +6,7 @@ run_cmake(COMPILE_OPTIONS)
 run_cmake(IMPORTED_GLOBAL)
 run_cmake(INCLUDE_DIRECTORIES)
 run_cmake(LINK_OPTIONS)
+run_cmake(LINK_DIRECTORIES)
 run_cmake(LINK_LIBRARIES)
 run_cmake(SOURCES)
 run_cmake(TYPE)