]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Add new absolute_paths_in_stderr (CCACHE_ABSSTDERR) feature
authorJoel Rosdahl <joel@rosdahl.net>
Tue, 11 Aug 2020 19:05:15 +0000 (21:05 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Wed, 12 Aug 2020 18:00:30 +0000 (20:00 +0200)
Motivation for the feature: When using the “Unix Makefiles” generator
with CMake, the compiler is executed in a different CWD than the “make”
command. This works well since absolute paths are passed to the
compiler. But combined with ccache’s base_dir feature the paths in
compiler error or warning messages will be incorrect since they will be
relative to the CWD of the compiler, not the CWD where the user or IDE
ran “make”. absolute_paths_in_stderr helps with this problem by
rewriting relative paths to absolute paths in the compiler’s stderr
output so that the user or IDE will see valid paths.

doc/MANUAL.adoc
src/Config.cpp
src/Config.hpp
src/Util.cpp
src/Util.hpp
test/suites/basedir.bash
unittest/test_Config.cpp

index 5b866421bc4d8c35d88061b8e8bc0470d8381cc9..a708b9644f2dd6c7e4862ced373434dd07eb1790 100644 (file)
@@ -304,6 +304,15 @@ Below is a list of available configuration settings. The corresponding
 environment variable name is indicated in parentheses after each configuration
 setting key.
 
+[[config_absolute_paths_in_stderr]] *absolute_paths_in_stderr* (*CCACHE_ABSSTDERR*)::
+
+    This setting specifies whether ccache should rewrite relative paths in the
+    compiler's standard error output to absolute paths. This can be useful if
+    you use <<config_base_dir,*base_dir*>> with a build system (e.g. CMake with
+    the "Unix Makefiles" generator) that executes the compiler in a different
+    working directory, which makes relative paths in compiler errors or
+    warnings incorrect. The default is false.
+
 [[config_base_dir]] *base_dir* (*CCACHE_BASEDIR*)::
 
     This setting should be an absolute path to a directory. ccache then
@@ -317,6 +326,9 @@ setting key.
 +
 See also the discussion under <<_compiling_in_different_directories,COMPILING
 IN DIFFERENT DIRECTORIES>>.
++
+You may also want to enable the
+<<config_absolute_paths_in_stderr,*absolute_paths_in_stderr*>> option.
 
 [[config_cache_dir]] *cache_dir* (*CCACHE_DIR*)::
 
index b1b7d44d88c4ac1be1dcf2d574a0c2eab63cfc79..5828e2277de0f638ac77c9e3513c40bbace105fc 100644 (file)
@@ -39,6 +39,7 @@ using nonstd::optional;
 namespace {
 
 enum class ConfigItem {
+  absolute_paths_in_stderr,
   base_dir,
   cache_dir,
   cache_dir_levels,
@@ -78,6 +79,7 @@ enum class ConfigItem {
 };
 
 const std::unordered_map<std::string, ConfigItem> k_config_key_table = {
+  {"absolute_paths_in_stderr", ConfigItem::absolute_paths_in_stderr},
   {"base_dir", ConfigItem::base_dir},
   {"cache_dir", ConfigItem::cache_dir},
   {"cache_dir_levels", ConfigItem::cache_dir_levels},
@@ -117,6 +119,7 @@ const std::unordered_map<std::string, ConfigItem> k_config_key_table = {
 };
 
 const std::unordered_map<std::string, std::string> k_env_variable_table = {
+  {"ABSSTDERR", "absolute_paths_in_stderr"},
   {"BASEDIR", "base_dir"},
   {"CC", "compiler"}, // Alias for CCACHE_COMPILER
   {"COMMENTS", "keep_comments_cpp"},
@@ -467,6 +470,9 @@ Config::get_string_value(const std::string& key) const
   }
 
   switch (it->second) {
+  case ConfigItem::absolute_paths_in_stderr:
+    return format_bool(m_absolute_paths_in_stderr);
+
   case ConfigItem::base_dir:
     return m_base_dir;
 
@@ -648,6 +654,10 @@ Config::set_item(const std::string& key,
   }
 
   switch (it->second) {
+  case ConfigItem::absolute_paths_in_stderr:
+    m_absolute_paths_in_stderr = parse_bool(value, env_var_key, negate);
+    break;
+
   case ConfigItem::base_dir:
     m_base_dir = Util::expand_environment_variables(value);
     if (!m_base_dir.empty()) { // The empty string means "disable"
index cb7a0f84d03ff302db46197e857659966a82ddf2..008d4ca08402ab22bde35a88f27e62dc7dbf8966 100644 (file)
@@ -39,6 +39,7 @@ public:
   Config(Config&) = default;
   Config& operator=(const Config&) = default;
 
+  bool absolute_paths_in_stderr() const;
   const std::string& base_dir() const;
   const std::string& cache_dir() const;
   uint32_t cache_dir_levels() const;
@@ -128,6 +129,7 @@ private:
   std::string m_primary_config_path;
   std::string m_secondary_config_path;
 
+  bool m_absolute_paths_in_stderr = false;
   std::string m_base_dir = "";
   std::string m_cache_dir;
   uint32_t m_cache_dir_levels = 2;
@@ -178,6 +180,12 @@ private:
   static std::string default_temporary_dir(const std::string& cache_dir);
 };
 
+inline bool
+Config::absolute_paths_in_stderr() const
+{
+  return m_absolute_paths_in_stderr;
+}
+
 inline const std::string&
 Config::base_dir() const
 {
index d2c1046bac7f89abb728aff6aa9a40b28b822a28..5c60c55258a8e383cb68b6485933a1893317b121 100644 (file)
@@ -149,6 +149,46 @@ split_at(string_view input, const char* separators)
   return result;
 }
 
+std::string
+rewrite_stderr_to_absolute_paths(string_view text)
+{
+  static const std::string in_file_included_from = "In file included from ";
+
+  std::string result;
+  for (auto line : Util::split_into_views(text, "\n")) {
+    // Rewrite <path> to <absolute path> in the following two cases, where X may
+    // be optional ANSI CSI sequences:
+    //
+    // In file included from X<path>X:1:
+    // X<path>X:1:2: ...
+
+    if (Util::starts_with(line, in_file_included_from)) {
+      result += in_file_included_from;
+      line = line.substr(in_file_included_from.length());
+    }
+    while (!line.empty() && line[0] == 0x1b) {
+      auto csi_seq = find_first_ansi_csi_seq(line);
+      result.append(csi_seq.data(), csi_seq.length());
+      line = line.substr(csi_seq.length());
+    }
+    size_t path_end = line.find(':');
+    if (path_end == nonstd::string_view::npos) {
+      result.append(line.data(), line.length());
+    } else {
+      std::string path(line.substr(0, path_end));
+      if (Stat::stat(path)) {
+        result += Util::real_path(path);
+        auto tail = line.substr(path_end);
+        result.append(tail.data(), tail.length());
+      } else {
+        result.append(line.data(), line.length());
+      }
+    }
+    result += '\n';
+  }
+  return result;
+}
+
 } // namespace
 
 namespace Util {
@@ -1176,6 +1216,11 @@ send_to_stderr(const Context& ctx, const std::string& text)
     }
   }
 
+  if (ctx.config.absolute_paths_in_stderr()) {
+    modified_text = rewrite_stderr_to_absolute_paths(*text_to_send);
+    text_to_send = &modified_text;
+  }
+
   try {
     write_fd(STDERR_FILENO, text_to_send->data(), text_to_send->length());
   } catch (Error& e) {
index 28c0af880c9f6ce8b96fc3b4826f1ab62d93d9c9..44ad8599bb3e3fcb0029e8c1ab79ae5171cd5d18 100644 (file)
@@ -368,7 +368,9 @@ bool same_program_name(const std::string& program_name,
                        const std::string& canonical_program_name);
 
 // Send `text` to STDERR_FILENO, optionally stripping ANSI color sequences if
-// `ctx.args_info.strip_diagnostics_colors` is true. Throws `Error` on error.
+// `ctx.args_info.strip_diagnostics_colors` is true and rewriting paths to
+// absolute if `ctx.config.absolute_paths_in_stderr` is true. Throws `Error` on
+// error.
 void send_to_stderr(const Context& ctx, const std::string& text);
 
 // Set the FD_CLOEXEC on file descriptor `fd`. This is a NOP on Windows.
index ff4a9959144002e04fc916cbf810a6bba72ccfdb..0ad4cff11d7657e20e6527c999a8823c7522d3d2 100644 (file)
@@ -268,4 +268,47 @@ EOF
         expect_stat 'cache miss' 1
         cd ..
     done
+
+    # -------------------------------------------------------------------------
+    TEST "Absolute paths in stderr"
+
+    cat <<EOF >test.c
+#include "test.h"
+#warning test.c
+EOF
+    cat <<EOF >test.h
+#warning test.h
+EOF
+    backdate test.h
+
+    pwd=$PWD.real
+    $REAL_COMPILER -c $pwd/test.c 2>reference.stderr
+
+    CCACHE_ABSSTDERR=1 CCACHE_BASEDIR="$pwd" $CCACHE_COMPILE -c $pwd/test.c 2>ccache.stderr
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files reference.stderr ccache.stderr
+
+    CCACHE_ABSSTDERR=1 CCACHE_BASEDIR="$pwd" $CCACHE_COMPILE -c $pwd/test.c 2>ccache.stderr
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_files reference.stderr ccache.stderr
+
+    if $REAL_COMPILER -fdiagnostics-color=always -c test.c 2>/dev/null; then
+        $REAL_COMPILER -fdiagnostics-color=always -c $pwd/test.c 2>reference.stderr
+
+        CCACHE_ABSSTDERR=1 CCACHE_BASEDIR="$pwd" $CCACHE_COMPILE -fdiagnostics-color=always -c $pwd/test.c 2>ccache.stderr
+        expect_stat 'cache hit (direct)' 2
+        expect_stat 'cache hit (preprocessed)' 0
+        expect_stat 'cache miss' 1
+        expect_equal_files reference.stderr ccache.stderr
+
+        CCACHE_ABSSTDERR=1 CCACHE_BASEDIR="$pwd" $CCACHE_COMPILE -fdiagnostics-color=always -c $pwd/test.c 2>ccache.stderr
+        expect_stat 'cache hit (direct)' 3
+        expect_stat 'cache hit (preprocessed)' 0
+        expect_stat 'cache miss' 1
+        expect_equal_files reference.stderr ccache.stderr
+    fi
 }
index d5d4fbd30ccc61cf0ec6bca6ada032d953d5a9a1..8e7c5add405f6eb0e5a4f8f69847eb793f28ce08 100644 (file)
@@ -374,6 +374,7 @@ TEST_CASE("Config::visit_items")
 
   Util::write_file(
     "test.conf",
+    "absolute_paths_in_stderr = true\n"
 #ifndef _WIN32
     "base_dir = /bd\n"
 #else
@@ -429,6 +430,7 @@ TEST_CASE("Config::visit_items")
   });
 
   std::vector<std::string> expected = {
+    "(test.conf) absolute_paths_in_stderr = true",
 #ifndef _WIN32
     "(test.conf) base_dir = /bd",
 #else