]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
fix: Handle spaces between target and colon in dependency files (#1166)
authorlouiscaron <caron_louis@yahoo.fr>
Wed, 5 Oct 2022 19:13:50 +0000 (21:13 +0200)
committerGitHub <noreply@github.com>
Wed, 5 Oct 2022 19:13:50 +0000 (21:13 +0200)
Support dependency files that are generated with spaces between the target and
the colon sign.

src/Depfile.cpp
unittest/test_Depfile.cpp

index ad63a7662756a1d51595c6344bd99cd38c811e8a..c5accfe171ec7fc0506ad44da0b25d87d4db652c 100644 (file)
@@ -146,6 +146,30 @@ tokenize(std::string_view file_content)
 {
   // A dependency file uses Makefile syntax. This is not perfect parser but
   // should be enough for parsing a regular dependency file.
+  // enhancement:
+  // - space between target and colon
+  // - no space between colon and first pre-requisite
+  // the later is pretty complex because of the windows paths which are
+  // identical to a target-colon-prerequisite without spaces (e.g. cat:/meow vs.
+  // c:/meow) here are the tests on windows gnu make 4.3 how it handles this:
+  //  + cat:/meow -> sees "cat" and "/meow"
+  //  + cat:\meow -> sees "cat" and "\meow"
+  //  + cat:\ meow -> sees "cat" and " meow"
+  //  + cat:c:/meow -> sees "cat" and "c:/meow"
+  //  + cat:c:\meow -> sees "cat" and "c:\meow"
+  //  + cat:c: -> target pattern contains no '%'.  Stop.
+  //  + cat:c:\ -> target pattern contains no '%'.  Stop.
+  //  + cat:c:/ -> sees "cat" and "c:/"
+  //  + cat:c:meow -> target pattern contains no '%'.  Stop.
+  //  + c:c:/meow -> sees "c" and "c:/meow"
+  //  + c:c:\meow -> sees "c" and "c:\meow"
+  //  + c:z:\meow -> sees "c" and "z:\meow"
+  //  + c:cd:\meow -> target pattern contains no '%'.  Stop.
+
+  // the logic for a windows path is:
+  //  - if there is a colon, if the previous token is 1 char long
+  //    and that the following char is a slash (fw or bw), then it is
+  //    a windows path
 
   std::vector<std::string> result;
   const size_t length = file_content.size();
@@ -153,19 +177,47 @@ tokenize(std::string_view file_content)
   size_t p = 0;
 
   while (p < length) {
-    // Each token is separated by whitespace.
-    if (isspace(file_content[p])) {
+    char c = file_content[p];
+
+    if (c == ':') {
+      if (p + 1 < length && !is_blank(token) && token.length() == 1) {
+        const char next = file_content[p + 1];
+        if (next == '/' || next == '\\') {
+          // only in this case, this is not a separator and colon is
+          // added to token
+          token.push_back(c);
+          ++p;
+          continue;
+        }
+      }
+    }
+    // Each token is separated by whitespace or a colon.
+    if (isspace(c) || c == ':') {
+      // chomp all spaces before next char
       while (p < length && isspace(file_content[p])) {
         ++p;
       }
       if (!is_blank(token)) {
+        // if there were spaces between a token and the : sign, the :
+        // must be added to the same token to make sure it is seen as
+        // a target and not as a dependency (ccache requirement)
+        if (p < length) {
+          const char next = file_content[p];
+          if (next == ':') {
+            token.push_back(next);
+            ++p;
+            // chomp all spaces before next char
+            while (p < length && isspace(file_content[p])) {
+              ++p;
+            }
+          }
+        }
         result.push_back(token);
       }
       token.clear();
       continue;
     }
 
-    char c = file_content[p];
     switch (c) {
     case '\\':
       if (p + 1 < length) {
@@ -182,7 +234,7 @@ tokenize(std::string_view file_content)
           ++p;
           break;
         // Backslash followed by newline is interpreted like a space, so simply
-        // the backslash.
+        // discard the backslash.
         case '\n':
           ++p;
           continue;
index 692a415395f57203481a0126383c1a669dda5eaf..e8ae88df4fc8fa2a5a7acfef608226cefb539bc5 100644 (file)
@@ -202,6 +202,195 @@ TEST_CASE("Depfile::tokenize")
     CHECK(result[0] == "cat.o:");
     CHECK(result[1] == "meow");
   }
+
+  SUBCASE("Parse depfile with a one space before colon")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat.o : meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+  }
+
+  SUBCASE("Parse depfile with a two spaces before colon")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat.o  : meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+  }
+
+  SUBCASE("Parse depfile with a plenty of spaces before colon")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat.o    :    meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+  }
+
+  SUBCASE("Parse depfile with no space between colon and dependency")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat.o:meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+  }
+
+  SUBCASE(
+    "Parse depfile with windows formatted filename (with backslashes in "
+    "target)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("e:\\cat.o: meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "e:\\cat.o:");
+    CHECK(result[1] == "meow");
+  }
+
+  SUBCASE(
+    "Parse depfile with windows formatted filename (with backslashes in "
+    "prerequisite)")
+  {
+    std::vector<std::string> result =
+      Depfile::tokenize("cat.o: c:\\meow\\purr");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "c:\\meow\\purr");
+  }
+
+  SUBCASE(
+    "Parse depfile with windows formatted filename (with slashes in target)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("e:/cat.o: meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "e:/cat.o:");
+    CHECK(result[1] == "meow");
+  }
+
+  SUBCASE(
+    "Parse depfile with windows formatted filename (with slashes in "
+    "prerequisite)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat.o: c:/meow/purr");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "c:/meow/purr");
+  }
+
+  SUBCASE(
+    "Parse depfile with windows formatted filename (with slashes and trailing "
+    "colon)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat.o: c: /meow/purr");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "c:");
+    CHECK(result[2] == "/meow/purr");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest1)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:/meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == "/meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest2)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:\\meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == "\\meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest3)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:\\ meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == " meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest4)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:c:/meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == "c:/meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest5)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:c:\\meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == "c:\\meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest6)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:c:");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == "c:");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest7)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:c:\\");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == "c:\\");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest8)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:c:/");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == "c:/");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest9)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat:c:meow");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "cat:");
+    CHECK(result[1] == "c:");
+    CHECK(result[2] == "meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest10)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("c:c:/meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "c:");
+    CHECK(result[1] == "c:/meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest11)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("c:c:\\meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "c:");
+    CHECK(result[1] == "c:\\meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest12)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("c:z:\\meow");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "c:");
+    CHECK(result[1] == "z:\\meow");
+  }
+
+  SUBCASE("Parse depfile with windows formatted filename (subtest13)")
+  {
+    std::vector<std::string> result = Depfile::tokenize("c:cd:\\meow");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "c:");
+    CHECK(result[1] == "cd:");
+    CHECK(result[2] == "\\meow");
+  }
 }
 
 TEST_SUITE_END();