Browse Source

Makefile: Fix regression in dependencies on relative includes

Since commit a13a5c948e (Replace use of CollapseCombinedPath with
CollapseFullPath, 2019-03-19, v3.15.0-rc1~361^2~1), one code path now
calls `CollapseFullPath` with a base path that may be relative.
Backport KWSys commit c6f8e24a3 (SystemTools: Fix CollapseFullPath with
relative base path, 2019-07-24) to handle such base paths.

This case occurs when a build tree is placed in a directory inside a
source tree such that CMake is willing to generate a relative path from
the build tree to the source tree.  Add a test covering this case.

Fixes: #19507
Brad King 6 years ago
parent
commit
d46bac5d38

+ 6 - 1
Source/kwsys/SystemTools.cxx

@@ -3394,8 +3394,13 @@ static void SystemToolsAppendComponents(
   static const std::string cur = ".";
   for (std::vector<std::string>::const_iterator i = first; i != last; ++i) {
     if (*i == up) {
-      if (out_components.size() > 1) {
+      // Remove the previous component if possible.  Ignore ../ components
+      // that try to go above the root.  Keep ../ components if they are
+      // at the beginning of a relative path (base path is relative).
+      if (out_components.size() > 1 && out_components.back() != up) {
         out_components.resize(out_components.size() - 1);
+      } else if (!out_components.empty() && out_components[0].empty()) {
+        out_components.emplace_back(std::move(*i));
       }
     } else if (!i->empty() && *i != cur) {
 #if __cplusplus >= 201103L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201103L)

+ 6 - 2
Source/kwsys/testSystemTools.cxx

@@ -684,9 +684,10 @@ static bool CheckRelativePaths()
 }
 
 static bool CheckCollapsePath(const std::string& path,
-                              const std::string& expected)
+                              const std::string& expected,
+                              const char* base = nullptr)
 {
-  std::string result = kwsys::SystemTools::CollapseFullPath(path);
+  std::string result = kwsys::SystemTools::CollapseFullPath(path, base);
   if (!kwsys::SystemTools::ComparePath(expected, result)) {
     std::cerr << "CollapseFullPath(" << path << ")  yielded " << result
               << " instead of " << expected << std::endl;
@@ -710,6 +711,9 @@ static bool CheckCollapsePath()
   res &= CheckCollapsePath("C:/", "C:/");
   res &= CheckCollapsePath("C:/../", "C:/");
   res &= CheckCollapsePath("C:/../../", "C:/");
+  res &= CheckCollapsePath("../b", "../../b", "../");
+  res &= CheckCollapsePath("../a/../b", "../b", "../rel");
+  res &= CheckCollapsePath("a/../b", "../rel/b", "../rel");
   return res;
 }
 

+ 5 - 0
Tests/RunCMake/BuildDepends/BuildUnderSource.c

@@ -0,0 +1,5 @@
+#include "BuildUnderSource.h"
+int main(void)
+{
+  return BUILD_UNDER_SOURCE;
+}

+ 9 - 0
Tests/RunCMake/BuildDepends/BuildUnderSource.cmake

@@ -0,0 +1,9 @@
+enable_language(C)
+include_directories(include)
+add_executable(BuildUnderSource BuildUnderSource.c)
+
+file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/check-$<LOWER_CASE:$<CONFIG>>.cmake CONTENT "
+set(check_pairs
+  \"$<TARGET_FILE:BuildUnderSource>|${CMAKE_CURRENT_SOURCE_DIR}/include/BuildUnderSource.h\"
+  )
+")

+ 3 - 0
Tests/RunCMake/BuildDepends/BuildUnderSource.step1.cmake

@@ -0,0 +1,3 @@
+file(WRITE "${RunCMake_TEST_SOURCE_DIR}/include/BuildUnderSource.h" [[
+#define BUILD_UNDER_SOURCE 1
+]])

+ 3 - 0
Tests/RunCMake/BuildDepends/BuildUnderSource.step2.cmake

@@ -0,0 +1,3 @@
+file(WRITE "${RunCMake_TEST_SOURCE_DIR}/include/BuildUnderSource.h" [[
+#define BUILD_UNDER_SOURCE 2
+]])

+ 15 - 1
Tests/RunCMake/BuildDepends/RunCMakeTest.cmake

@@ -9,7 +9,9 @@ endif()
 
 function(run_BuildDepends CASE)
   # Use a single build tree for a few tests without cleaning.
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${CASE}-build)
+  if(NOT RunCMake_TEST_BINARY_DIR)
+    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${CASE}-build)
+  endif()
   set(RunCMake_TEST_NO_CLEAN 1)
   if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
     set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
@@ -44,6 +46,18 @@ endif()
 run_BuildDepends(Custom-Symbolic-and-Byproduct)
 run_BuildDepends(Custom-Always)
 
+# Test header dependencies with a build tree underneath a source tree.
+set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/BuildUnderSource")
+set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/BuildUnderSource/build")
+file(REMOVE_RECURSE "${RunCMake_TEST_SOURCE_DIR}")
+file(MAKE_DIRECTORY "${RunCMake_TEST_SOURCE_DIR}/include")
+foreach(f CMakeLists.txt BuildUnderSource.cmake BuildUnderSource.c)
+  configure_file("${RunCMake_SOURCE_DIR}/${f}" "${RunCMake_TEST_SOURCE_DIR}/${f}" COPYONLY)
+endforeach()
+run_BuildDepends(BuildUnderSource)
+unset(RunCMake_TEST_BINARY_DIR)
+unset(RunCMake_TEST_SOURCE_DIR)
+
 if(RunCMake_GENERATOR MATCHES "Make")
   run_BuildDepends(MakeCustomIncludes)
   if(NOT "${RunCMake_BINARY_DIR}" STREQUAL "${RunCMake_SOURCE_DIR}")