Imported Upstream version 4.1 upstream/4.1
authorJinWang An <jinwang.an@samsung.com>
Tue, 3 Aug 2021 07:30:25 +0000 (16:30 +0900)
committerJinWang An <jinwang.an@samsung.com>
Tue, 3 Aug 2021 07:30:25 +0000 (16:30 +0900)
84 files changed:
.github/workflows/build.yaml
CMakeLists.txt
cmake/CcacheVersion.cmake
cmake/GenerateConfigurationFile.cmake
cmake/StandardSettings.cmake
cmake/StandardWarnings.cmake
cmake/StdAtomic.cmake [new file with mode: 0644]
cmake/config.h.in
doc/AUTHORS.adoc
doc/CMakeLists.txt
doc/INSTALL.md
doc/MANUAL.adoc
doc/NEWS.adoc
dockerfiles/ubuntu-14.04/Dockerfile
dockerfiles/ubuntu-16.04/Dockerfile
dockerfiles/ubuntu-18.04/Dockerfile [new file with mode: 0644]
dockerfiles/ubuntu-20.04/Dockerfile
misc/test-all-systems
src/.clang-tidy
src/Args.cpp
src/Args.hpp
src/ArgsInfo.hpp
src/CMakeLists.txt
src/CacheEntryReader.cpp
src/Config.cpp
src/Config.hpp
src/Context.cpp
src/Context.hpp
src/Depfile.cpp [new file with mode: 0644]
src/Depfile.hpp [new file with mode: 0644]
src/Hash.cpp
src/InodeCache.cpp
src/Lockfile.cpp
src/Logging.cpp
src/Logging.hpp
src/Manifest.cpp
src/MiniTrace.cpp
src/ProgressBar.cpp
src/Result.cpp
src/ResultDumper.cpp
src/ResultExtractor.cpp
src/ResultRetriever.cpp
src/Stat.cpp
src/Statistics.cpp
src/Util.cpp
src/Util.hpp
src/ZstdCompressor.cpp
src/argprocessing.cpp
src/assertions.cpp
src/ccache.cpp
src/ccache.hpp
src/cleanup.cpp
src/compopt.cpp
src/compress.cpp
src/execute.cpp
src/fmtmacros.hpp [new file with mode: 0644]
src/hashutil.cpp
src/third_party/CMakeLists.txt
src/third_party/blake3/CMakeLists.txt
src/third_party/blake3/blake3_cpu_supports_avx2.h [new file with mode: 0644]
src/third_party/blake3/blake3_dispatch_ccache.c [new file with mode: 0644]
src/third_party/doctest.h
test/suites/base.bash
test/suites/color_diagnostics.bash
test/suites/depend.bash
test/suites/direct.bash
test/suites/modules.bash
test/suites/profiling.bash
test/suites/profiling_clang.bash
test/suites/profiling_hip_clang.bash
test/suites/sanitize_blacklist.bash
test/suites/serialize_diagnostics.bash
test/suites/split_dwarf.bash
unittest/.clang-tidy
unittest/CMakeLists.txt
unittest/TestUtil.cpp
unittest/main.cpp
unittest/test_Args.cpp
unittest/test_Config.cpp
unittest/test_Depfile.cpp [new file with mode: 0644]
unittest/test_Statistics.cpp
unittest/test_Util.cpp
unittest/test_argprocessing.cpp
unittest/test_ccache.cpp

index 5bd2fec..705f1a7 100644 (file)
@@ -3,42 +3,133 @@ on:
   push:
   pull_request:
 
+env:
+  CTEST_OUTPUT_ON_FAILURE: ON
+  VERBOSE: 1
+
 jobs:
-  # These test the standard build on several systems with GCC + Clang.
-  standard_tests:
-    name: ${{ matrix.os }} & ${{ matrix.compiler.CC }}
-    runs-on: ${{ matrix.os }}
+  build_and_test:
+    env:
+      CMAKE_GENERATOR: Ninja
+    
+    name: ${{ matrix.config.os }}-${{ matrix.config.compiler }}-${{ matrix.config.version }}
+    runs-on: ${{ matrix.config.os }}
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-16.04, ubuntu-18.04, ubuntu-20.04, macos-10.15]
-        compiler:
-          - CC: gcc
-            CXX: g++
-          - CC: clang
-            CXX: clang++
+        config:
+          - os: ubuntu-16.04
+            compiler: gcc
+            version: "4.8" # results in 4.8.5
+
+          - os: ubuntu-16.04
+            compiler: gcc
+            version: "5"
+
+          - os: ubuntu-18.04
+            compiler: gcc
+            version: "6"
+
+          - os: ubuntu-18.04
+            compiler: gcc
+            version: "7"
+
+          - os: ubuntu-18.04
+            compiler: gcc
+            version: "8"
+
+          - os: ubuntu-18.04
+            compiler: gcc
+            version: "9"
+
+          - os: ubuntu-20.04
+            compiler: gcc
+            version: "10"
+
+          # Enable after https://github.com/ccache/ccache/pull/693
+          # - os: ubuntu-16.04
+          #   compiler: clang
+          #   version: "3.5"
+
+          # Enable after https://github.com/ccache/ccache/pull/693
+          # - os: ubuntu-16.04
+          #   compiler: clang
+          #   version: "5.0"
+
+          - os: ubuntu-16.04
+            compiler: clang
+            version: "6.0"
+
+          - os: ubuntu-18.04
+            compiler: clang
+            version: "7"
+
+          - os: ubuntu-18.04
+            compiler: clang
+            version: "8"
 
+          - os: ubuntu-20.04
+            compiler: clang
+            version: "9"
+
+          - os: ubuntu-20.04
+            compiler: clang
+            version: "10"
+
+          - os: macOS-latest
+            compiler: xcode
+            version: "10.3"
+
+          - os: macOS-latest
+            compiler: xcode
+            version: "11.7"
+
+          - os: macOS-latest
+            compiler: xcode
+            version: "12.2"
     steps:
-      - name: Get source
-        uses: actions/checkout@v2
+      - name: Install dependencies
+        run: |
+          if [ "${{ runner.os }}" = "Linux" ]; then
+            if [ "${{ matrix.config.os }}" = "ubuntu-20.04" ]; then
+              sudo apt-get install -y ninja-build elfutils libzstd-dev
+            else
+              sudo apt-get install -y ninja-build elfutils libzstd1-dev
+            fi
+
+            if [ "${{ matrix.config.compiler }}" = "gcc" ]; then
+              echo "CC=gcc-${{ matrix.config.version }}" >> $GITHUB_ENV
+              echo "CXX=g++-${{ matrix.config.version }}" >> $GITHUB_ENV
+
+              sudo apt install -y g++-${{ matrix.config.version }} g++-${{ matrix.config.version }}-multilib
+            else
+              echo "CC=clang-${{ matrix.config.version }}" >> $GITHUB_ENV
+              echo "CXX=clang++-${{ matrix.config.version }}" >> $GITHUB_ENV
 
-      - name: Install dependencies (Ubuntu 16 & 18)
-        if: startsWith(matrix.os, 'ubuntu') && matrix.os != 'ubuntu-20.04'
-        run: sudo apt-get install elfutils libzstd1-dev
+              sudo apt update
+              sudo apt install -y clang-${{ matrix.config.version }} g++-multilib
+            fi
+          elif [ "${{ runner.os }}" = "macOS" ]; then
+            brew install ninja
+            if [ "${{ matrix.config.compiler }}" = "gcc" ]; then
+              brew install gcc@${{ matrix.config.version }}
+              echo "CC=gcc-${{ matrix.config.version }}" >> $GITHUB_ENV
+              echo "CXX=g++-${{ matrix.config.version }}" >> $GITHUB_ENV
+            else
+              sudo xcode-select -switch /Applications/Xcode_${{ matrix.config.version }}.app
+              echo "CC=clang" >> $GITHUB_ENV
+              echo "CXX=clang++" >> $GITHUB_ENV
+            fi
+          fi
 
-      - name: Install dependencies (Ubuntu 20)
-        if: matrix.os == 'ubuntu-20.04'
-        run: sudo apt-get install elfutils libzstd-dev
+      - name: Get source
+        uses: actions/checkout@v2
 
       - name: Build and test
         run: ci/build
         env:
-          CC: ${{ matrix.compiler.CC }}
-          CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI
-          CTEST_OUTPUT_ON_FAILURE: ON
-          CXX: ${{ matrix.compiler.CXX }}
           ENABLE_CACHE_CLEANUP_TESTS: true
-          VERBOSE: 1
+          CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI
 
       - name: Collect testdir from failed tests
         if: failure()
@@ -48,7 +139,7 @@ jobs:
         if: failure()
         uses: actions/upload-artifact@v2
         with:
-          name: ${{ matrix.os }} - ${{ matrix.compiler.CC }} - testdir.tar.xz
+          name: ${{ matrix.config.os }}-${{ matrix.config.compiler }}-{{ matrix.config.version }}-testdir.tar.xz
           path: testdir.tar.xz
 
   specific_tests:
@@ -58,14 +149,14 @@ jobs:
       fail-fast: false
       matrix:
         config:
-          - name: Linux GCC debug + in source + tracing
+          - name: Linux GCC debug + C++14 + in source + tracing
             os: ubuntu-18.04
             CC: gcc
             CXX: g++
             ENABLE_CACHE_CLEANUP_TESTS: 1
             BUILDDIR: .
             CCACHE_LOC: .
-            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=Debug -DENABLE_TRACING=1
+            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=Debug -DENABLE_TRACING=1 -DCMAKE_CXX_STANDARD=14
             apt_get: elfutils libzstd1-dev
 
           - name: Linux GCC 32-bit
@@ -101,18 +192,10 @@ jobs:
             CC: x86_64-w64-mingw32-gcc-posix
             CXX: x86_64-w64-mingw32-g++-posix
             ENABLE_CACHE_CLEANUP_TESTS: 1
-            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DCMAKE_SYSTEM_NAME=Windows -DZSTD_FROM_INTERNET=ON
+            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DCMAKE_SYSTEM_NAME=Windows -DZSTD_FROM_INTERNET=ON -DSTATIC_LINK=ON
             RUN_TESTS: unittest-in-wine
             apt_get: elfutils mingw-w64 wine
 
-          - name: Linux GCC 4.8.5
-            os: ubuntu-16.04
-            CC: gcc-4.8
-            CXX: g++-4.8
-            ENABLE_CACHE_CLEANUP_TESTS: 1
-            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DWARNINGS_AS_ERRORS=OFF
-            apt_get: elfutils libzstd1-dev g++-4.8
-
           - name: Clang address & UB sanitizer
             os: ubuntu-20.04
             CC: clang
@@ -144,19 +227,19 @@ jobs:
             CC: gcc
             CXX: g++
             SPECIAL: build-and-verify-source-package
-            apt_get: elfutils libzstd-dev ninja-build asciidoc xsltproc
+            apt_get: elfutils libzstd-dev ninja-build asciidoc xsltproc docbook-xml docbook-xsl
 
           - name: HTML documentation
             os: ubuntu-18.04
             EXTRA_CMAKE_BUILD_FLAGS: --target doc-html
             RUN_TESTS: none
-            apt_get: libzstd1-dev asciidoc
+            apt_get: libzstd1-dev asciidoc docbook-xml docbook-xsl
 
           - name: Manual page
             os: ubuntu-18.04
             EXTRA_CMAKE_BUILD_FLAGS: --target doc-man-page
             RUN_TESTS: none
-            apt_get: libzstd1-dev asciidoc xsltproc
+            apt_get: libzstd1-dev asciidoc xsltproc docbook-xml docbook-xsl
 
           - name: Clang-Tidy
             os: ubuntu-18.04
@@ -188,7 +271,6 @@ jobs:
           CCACHE_LOC: ${{ matrix.config.CCACHE_LOC }}
           CFLAGS: ${{ matrix.config.CFLAGS }}
           CMAKE_PARAMS: ${{ matrix.config.CMAKE_PARAMS }}
-          CTEST_OUTPUT_ON_FAILURE: ON
           CXX: ${{ matrix.config.CXX }}
           CXXFLAGS: ${{ matrix.config.CXXFLAGS }}
           ENABLE_CACHE_CLEANUP_TESTS: ${{ matrix.config.ENABLE_CACHE_CLEANUP_TESTS }}
@@ -196,7 +278,6 @@ jobs:
           LDFLAGS: ${{ matrix.config.LDFLAGS }}
           RUN_TESTS: ${{ matrix.config.RUN_TESTS }}
           SPECIAL: ${{ matrix.config.SPECIAL }}
-          VERBOSE: 1
         run: ci/build
 
       - name: Collect testdir from failed tests
@@ -222,8 +303,6 @@ jobs:
 
       - name: Run Clang-Format in check mode
         run: misc/format-files --all --check
-        env:
-          VERBOSE: 1
 
   codespell:
     name: Spelling
index 77fbd2c..1b36dc3 100644 (file)
@@ -3,7 +3,9 @@ cmake_minimum_required(VERSION 3.4.3)
 project(ccache LANGUAGES C CXX ASM)
 set(CMAKE_PROJECT_DESCRIPTION "a fast C/C++ compiler cache")
 
-set(CMAKE_CXX_STANDARD 11)
+if(NOT "${CMAKE_CXX_STANDARD}")
+  set(CMAKE_CXX_STANDARD 11)
+endif()
 set(CMAKE_CXX_STANDARD_REQUIRED YES)
 set(CMAKE_CXX_EXTENSIONS NO)
 
@@ -17,6 +19,36 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
 
 #
+# Minimum compiler requirements (fail gracefully instead of producing cryptic
+# C++ error messages)
+#
+
+# Clang 3.4 and AppleClang 6.0 fail to compile doctest.
+# GCC 4.8.4 is known to work OK but give warnings.
+if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5)
+   OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8.4)
+   OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0))
+  message(
+    FATAL_ERROR
+    "The compiler you are using is too old, sorry.\n"
+    "You need one listed here: https://ccache.dev/platform-compiler-language-support.html")
+endif()
+
+# All Clang problems / special handling ccache has are because of version 3.5.
+# All GCC problems / special handling ccache has are because of version 4.8.4.
+if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.6)
+    OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
+  message(
+    WARNING
+    "The compiler you are using is rather old.\n"
+    "If anything goes wrong you might be better off with one listed here:"
+    " https://ccache.dev/platform-compiler-language-support.html")
+
+  # Warnings from old compilers are probably useless anyway.
+  option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" FALSE)
+endif()
+
+#
 # Settings
 #
 include(StandardSettings)
@@ -65,6 +97,10 @@ find_package(zstd 1.1.2 REQUIRED)
 include(CodeAnalysis)
 option(ENABLE_TRACING "Enable possibility to use internal ccache tracing" OFF)
 
+if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+  option(STATIC_LINK "Link statically with system libraries" OFF)
+endif()
+
 #
 # Source code
 #
index fcac1f5..0af8f03 100644 (file)
 #    version_info has not been substituted). In this case we fail the
 #    configuration.
 # 3. Building from a Git repository. In this case the version will be a proper
-#    version if building a tagged commit, otherwise "branch.hash(+dirty)".
+#    version if building a tagged commit, otherwise "branch.hash(+dirty)". In
+#    case Git is not available, the version will be "unknown".
 
-set(version_info "7c44b8c45bd6bc185bf5bd5666f36fcac092d917 HEAD, tag: v4.0, origin/master, origin/HEAD, master")
+set(version_info "897b6065398b5e80402ae1c51a60a2cefc765ed1 HEAD, tag: v4.1, origin/master, origin/HEAD, master")
 
 if(version_info MATCHES "^([0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])[0-9a-f]* (.*)")
   # Scenario 1.
@@ -30,32 +31,33 @@ elseif(EXISTS "${CMAKE_SOURCE_DIR}/.git")
   # Scenario 3.
   find_package(Git QUIET)
   if(NOT GIT_FOUND)
-    message(SEND_ERROR "Could not find git")
-  endif()
+    set(VERSION "unknown")
+    message(WARNING "Could not find git")
+  else()
+    macro(git)
+      execute_process(
+        COMMAND "${GIT_EXECUTABLE}" ${ARGN}
+        OUTPUT_VARIABLE git_stdout OUTPUT_STRIP_TRAILING_WHITESPACE
+        ERROR_VARIABLE git_stderr ERROR_STRIP_TRAILING_WHITESPACE)
+    endmacro()
 
-  macro(git)
-    execute_process(
-      COMMAND "${GIT_EXECUTABLE}" ${ARGN}
-      OUTPUT_VARIABLE git_stdout OUTPUT_STRIP_TRAILING_WHITESPACE
-      ERROR_VARIABLE git_stderr ERROR_STRIP_TRAILING_WHITESPACE)
-  endmacro()
+    git(describe --abbrev=8 --dirty)
+    if(git_stdout MATCHES "^v([^-]+)(-dirty)?$")
+      set(VERSION "${CMAKE_MATCH_1}")
+      if(NOT "${CMAKE_MATCH_2}" STREQUAL "")
+        set(VERSION "${VERSION}+dirty")
+      endif()
+    elseif(git_stdout MATCHES "^v[^-]+-[0-9]+-g([0-9a-f]+)(-dirty)?$")
+      set(hash "${CMAKE_MATCH_1}")
+      set(dirty "${CMAKE_MATCH_2}")
+      string(REPLACE "-" "+" dirty "${dirty}")
 
-  git(describe --abbrev=8 --dirty)
-  if(git_stdout MATCHES "^v([^-]+)(-dirty)?$")
-    set(VERSION "${CMAKE_MATCH_1}")
-    if(NOT "${CMAKE_MATCH_2}" STREQUAL "")
-      set(VERSION "${VERSION}+dirty")
-    endif()
-  elseif(git_stdout MATCHES "^v[^-]+-[0-9]+-g([0-9a-f]+)(-dirty)?$")
-    set(hash "${CMAKE_MATCH_1}")
-    set(dirty "${CMAKE_MATCH_2}")
-    string(REPLACE "-" "+" dirty "${dirty}")
+      git(rev-parse --abbrev-ref HEAD)
+      set(branch "${git_stdout}")
 
-    git(rev-parse --abbrev-ref HEAD)
-    set(branch "${git_stdout}")
-
-    set(VERSION "${branch}.${hash}${dirty}")
-  endif() # else: fail below
+      set(VERSION "${branch}.${hash}${dirty}")
+    endif() # else: fail below
+  endif()
 endif()
 
 if(VERSION STREQUAL "")
index 582fedb..a21861f 100644 (file)
@@ -44,6 +44,21 @@ foreach(func IN ITEMS ${functions})
   check_function_exists(${func} ${func_var})
 endforeach()
 
+include(CheckCSourceCompiles)
+set(CMAKE_REQUIRED_LINK_OPTIONS -pthread)
+check_c_source_compiles(
+  [=[
+    #include <pthread.h>
+    int main()
+    {
+      pthread_mutexattr_t attr;
+      (void)pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
+      return 0;
+    }
+  ]=]
+  HAVE_PTHREAD_MUTEX_ROBUST)
+set(CMAKE_REQUIRED_LINK_OPTIONS)
+
 include(CheckStructHasMember)
 check_struct_has_member("struct stat" st_ctim sys/stat.h
                         HAVE_STRUCT_STAT_ST_CTIM)
@@ -53,7 +68,17 @@ check_struct_has_member("struct statfs" f_fstypename sys/mount.h
                         HAVE_STRUCT_STATFS_F_FSTYPENAME)
 
 include(CheckCXXCompilerFlag)
-check_cxx_compiler_flag(-mavx2 HAVE_AVX2)
+
+# Old GCC versions don't have the required header support.
+# Old Apple Clang versions seem to support -mavx2 but not the target
+# attribute that's used to enable AVX2 for a certain function.
+if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
+   OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0))
+  message(STATUS "Detected unsupported compiler for HAVE_AVX2 - disabled")
+  set(HAVE_AVX2 FALSE)
+else()
+  check_cxx_compiler_flag(-mavx2 HAVE_AVX2)
+endif()
 
 list(APPEND CMAKE_REQUIRED_LIBRARIES ws2_32)
 list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES ws2_32)
index 755ff9f..a0f0189 100644 (file)
@@ -6,7 +6,7 @@ add_library(standard_settings INTERFACE)
 # Not supported in CMake 3.4: target_compile_features(project_options INTERFACE
 # c_std_11 cxx_std_11)
 
-if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|Clang$")
+if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|(Apple)?Clang$")
   option(ENABLE_COVERAGE "Enable coverage reporting for GCC/Clang" FALSE)
   if(ENABLE_COVERAGE)
     target_compile_options(standard_settings INTERFACE --coverage -O0 -g)
@@ -47,6 +47,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|Clang$")
       INTERFACE -fsanitize=${SANITIZER})
   endforeach()
 
+  include(StdAtomic)
+
 elseif(MSVC)
   target_compile_options(standard_settings INTERFACE /std:c++latest /Zc:preprocessor /Zc:__cplusplus /D_CRT_SECURE_NO_WARNINGS)
 endif()
index dbb45f1..3507737 100644 (file)
@@ -95,8 +95,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
       -Wno-global-constructors
       -Wno-implicit-fallthrough
       -Wno-padded
+      -Wno-shadow # Warnings in fmtlib
       -Wno-shorten-64-to-32
       -Wno-sign-conversion
+      -Wno-signed-enum-bitfield # Warnings in fmtlib
       -Wno-weak-vtables
       -Wno-old-style-cast)
 
diff --git a/cmake/StdAtomic.cmake b/cmake/StdAtomic.cmake
new file mode 100644 (file)
index 0000000..aa3bb4b
--- /dev/null
@@ -0,0 +1,31 @@
+# Check if std::atomic needs -latomic
+
+include(CheckCXXSourceCompiles)
+
+set(CMAKE_REQUIRED_FLAGS ${CMAKE_CXX11_STANDARD_COMPILE_OPTION})
+set(
+  check_std_atomic_source_code
+  [=[
+    #include <atomic>
+    int main()
+    {
+      std::atomic<long long> x;
+      (void)x.load();
+      return 0;
+    }
+  ]=])
+
+check_cxx_source_compiles("${check_std_atomic_source_code}" std_atomic_without_libatomic)
+
+if(NOT std_atomic_without_libatomic)
+  set(CMAKE_REQUIRED_LIBRARIES atomic)
+  check_cxx_source_compiles("${check_std_atomic_source_code}" std_atomic_with_libatomic)
+  set(CMAKE_REQUIRED_LIBRARIES)
+  if(NOT std_atomic_with_libatomic)
+    message(FATAL_ERROR "Toolchain doesn't support std::atomic with nor without -latomic")
+  else()
+    target_link_libraries(standard_settings INTERFACE atomic)
+  endif()
+endif()
+
+set(CMAKE_REQUIRED_FLAGS)
index 11fdf43..28a0706 100644 (file)
 
 #cmakedefine MTR_ENABLED
 
-/* Define to 1 if you have the `asctime_r' function. */
+// Define if you have the "asctime_r" function.
 #cmakedefine HAVE_ASCTIME_R
 
-/* Define to 1 if your compiler supports AVX2. */
+// Define if your compiler supports AVX2.
 #cmakedefine HAVE_AVX2
 
-/* Define to 1 if you have the `geteuid' function. */
+// Define if you have the "geteuid" function.
 #cmakedefine HAVE_GETEUID
 
-/* Define to 1 if you have the `getopt_long' function. */
+// Define if you have the "getopt_long" function.
 #cmakedefine HAVE_GETOPT_LONG
 
-/* Define to 1 if you have the `getpwuid' function. */
+// Define if you have the "getpwuid" function.
 #cmakedefine HAVE_GETPWUID
 
-/* Define to 1 if you have the `gettimeofday' function. */
+// Define if you have the "gettimeofday" function.
 #cmakedefine HAVE_GETTIMEOFDAY
 
-/* Define to 1 if you have the <linux/fs.h> header file. */
+// Define if you have the <linux/fs.h> header file.
 #cmakedefine HAVE_LINUX_FS_H
 
-/* Define to 1 if the system has the type `long long'. */
+// Define if the system has the type "long long".
 #cmakedefine HAVE_LONG_LONG
 
-/* Define to 1 if you have the `mkstemp' function. */
+// Define if you have the "mkstemp" function.
 #cmakedefine HAVE_MKSTEMP
 
-/* Define to 1 if you have the `posix_fallocate. */
+// Define if you have the "posix_fallocate.
 #cmakedefine HAVE_POSIX_FALLOCATE
 
-/* Define to 1 if you have the <pwd.h> header file. */
+// Define if you have the <pwd.h> header file.
 #cmakedefine HAVE_PWD_H
 
-/* Define to 1 if you have the `realpath' function. */
+// Define if you have the "realpath"" function.
 #cmakedefine HAVE_REALPATH
 
-/* Define to 1 if you have the `setenv' function. */
+// Define if you have the "setenv" function.
 #cmakedefine HAVE_SETENV
 
-/* Define to 1 if you have the `strndup' function. */
+// Define if you have the "strndup" function.
 #cmakedefine HAVE_STRNDUP
 
-/* Define to 1 if `f_fstypename' is a member of `struct statfs'. */
+// Define if "f_fstypename" is a member of "struct statfs".
 #cmakedefine HAVE_STRUCT_STATFS_F_FSTYPENAME
 
-/* Define to 1 if `st_ctim' is a member of `struct stat'. */
+// Define if "st_ctim" is a member of "struct stat".
 #cmakedefine HAVE_STRUCT_STAT_ST_CTIM
 
-/* Define to 1 if `st_mtim' is a member of `struct stat'. */
+// Define if "st_mtim" is a member of "struct stat".
 #cmakedefine HAVE_STRUCT_STAT_ST_MTIM
 
-/* Define to 1 if you have the `syslog' function. */
+// Define if you have the "syslog" function.
 #cmakedefine HAVE_SYSLOG
 
-/* Define to 1 if you have the <syslog.h> header file. */
+// Define if you have the <syslog.h> header file.
 #cmakedefine HAVE_SYSLOG_H
 
-/* Define to 1 if you have the <sys/clonefile.h> header file. */
+// Define if you have the <sys/clonefile.h> header file.
 #cmakedefine HAVE_SYS_CLONEFILE_H
 
-/* Define to 1 if you have the <sys/ioctl.h> header file. */
+// Define if you have the <sys/ioctl.h> header file.
 #cmakedefine HAVE_SYS_IOCTL_H
 
-/* Define to 1 if you have the <sys/mman.h> header file. */
+// Define if you have the <sys/mman.h> header file.
 #cmakedefine HAVE_SYS_MMAN_H
 
-/* Define to 1 if you have the <sys/time.h> header file. */
+// Define if you have the <sys/time.h> header file.
 #cmakedefine HAVE_SYS_TIME_H
 
-/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+// Define if you have <sys/wait.h> that is POSIX.1 compatible.
 #cmakedefine HAVE_SYS_WAIT_H
 
-/* Define to 1 if you have the <sys/file.h> header file. */
+// Define if you have the <sys/file.h> header file.
 #cmakedefine HAVE_SYS_FILE_H
 
-/* Define to 1 if you have the <termios.h> header file. */
+// Define if you have the <termios.h> header file.
 #cmakedefine HAVE_TERMIOS_H
 
-/* Define to 1 if you have the <dirent.h> header file. */
+// Define if you have the <dirent.h> header file.
 #cmakedefine HAVE_DIRENT_H
 
-/* Define to 1 if you have the <strings.h> header file. */
+// Define if you have the <strings.h> header file.
 #cmakedefine HAVE_STRINGS_H
 
-/* Define to 1 if you have the <unistd.h> header file. */
+// Define if you have the <unistd.h> header file.
 #cmakedefine HAVE_UNISTD_H
 
-/* Define to 1 if you have the <utime.h> header file. */
+// Define if you have the <utime.h> header file.
 #cmakedefine HAVE_UTIME_H
 
-/* Define to 1 if you have the <sys/utime.h> header file. */
+// Define if you have the <sys/utime.h> header file.
 #cmakedefine HAVE_SYS_UTIME_H
 
-/* Define to 1 if you have the <varargs.h> header file. */
+// Define if you have the <varargs.h> header file.
 #cmakedefine HAVE_VARARGS_H
 
-/* Define to 1 if you have the `unsetenv' function. */
+// Define if you have the "unsetenv" function.
 #cmakedefine HAVE_UNSETENV
 
-/* Define to 1 if you have the `utimes' function. */
+// Define if you have the "utimes" function.
 #cmakedefine HAVE_UTIMES
+
+// Define if you have the "PTHREAD_MUTEX_ROBUST" constant.
+#cmakedefine HAVE_PTHREAD_MUTEX_ROBUST
index cc0df94..eb15cef 100644 (file)
@@ -123,6 +123,7 @@ Ccache is a collective work with contributions from many people, including:
 * Wilson Snyder
 * Xavier René-Corail
 * Yiding Jia
+* Yoshimasa Niwa
 * Yvan Janssens
 
 Thanks!
index 53f6351..b5c9f24 100644 (file)
@@ -55,10 +55,14 @@ else()
     )
     add_custom_command(
       OUTPUT ccache.1
-      COMMAND a2x --doctype manpage --format manpage MANUAL.xml
+      COMMAND ${A2X_EXE} --doctype manpage --format manpage MANUAL.xml
       MAIN_DEPENDENCY MANUAL.xml
     )
-    add_custom_target(doc-man-page DEPENDS ccache.1)
+    add_custom_target(doc-man-page ALL DEPENDS ccache.1)
+    install(
+      FILES "${CMAKE_CURRENT_BINARY_DIR}/ccache.1"
+      DESTINATION "${CMAKE_INSTALL_MANDIR}/man1"
+    )
     set(doc_files "${doc_files}" ccache.1)
   endif()
 
index f27053f..77c2a72 100644 (file)
@@ -7,7 +7,9 @@ Prerequisites
 To build ccache you need:
 
 - CMake 3.4.3 or newer.
-- A C++11 compiler.
+- A C++11 compiler. See [Supported platforms, compilers and
+  languages](https://ccache.dev/platform-compiler-language-support.html) for
+  details.
 - A C99 compiler.
 - [libzstd](https://www.zstd.net). If you don't have libzstd installed and
   can't or don't want to install it in a standard system location, there are
@@ -23,6 +25,9 @@ Optional:
 - GNU Bourne Again SHell (bash) for tests.
 - [AsciiDoc](https://www.methods.co.nz/asciidoc/) to build the HTML
   documentation.
+  - Tip: On Debian-based systems (e.g. Ubuntu), install the `docbook-xml` and
+    `docbook-xsl` packages in addition to `asciidoc`. Without the former the
+    man page generation will be very slow.
 - [xsltproc](http://xmlsoft.org/XSLT/xsltproc2.html) to build the man page.
 - [Python](https://www.python.org) to debug and run the performance test suite.
 
index 25f345a..b7d8912 100644 (file)
@@ -8,7 +8,7 @@ CCACHE(1)
 Name
 ----
 
-Ccache - a fast C/C++ compiler cache
+ccache - a fast C/C++ compiler cache
 
 
 Synopsis
@@ -95,11 +95,19 @@ Common options
     Clear the entire cache, removing all cached files, but keeping the
     configuration file.
 
-*`-d`*, *`--directory`* _PATH_
+*`--config-path`* _PATH_::
 
-    Let the subsequent command line options operate on cache directory PATH
-    instead of the default for. For example, to show statistics for a cache
+    Let the subsequent command line options operate on configuration file
+    _PATH_ instead of the default. Using this option has the same effect as
+    setting the environment variable `CCACHE_CONFIGPATH` temporarily.
+
+*`-d`*, *`--directory`* _PATH_::
+
+    Let the subsequent command line options operate on cache directory _PATH_
+    instead of the default. For example, to show statistics for a cache
     directory at `/shared/ccache` you can run `ccache -d /shared/ccache -s`.
+    Using this option has the same effect as setting the environment variable
+    `CCACHE_DIR` temporarily.
 
 *`--evict-older-than`* _AGE_::
 
@@ -260,14 +268,14 @@ The location of the primary (cache-specific) configuration is determined like
 this:
 
 1. If *CCACHE_CONFIGPATH* is set, use that path.
-2. Otherwise, if <<config_ccache_dir,*ccache_dir*>> (*CCACHE_DIR*) is set then
+2. Otherwise, if <<config_cache_dir,*cache_dir*>> (*CCACHE_DIR*) is set then
    use *<ccache_dir>/ccache.conf*.
 3. Otherwise, if there is a legacy *$HOME/.ccache* directory then use
-   *$HOME/.ccache/ccache.conf.
+   *$HOME/.ccache/ccache.conf*.
 4. Otherwise, if *XDG_CONFIG_HOME* is set then use
    *$XDG_CONFIG_HOME/ccache/ccache.conf*.
 5. Otherwise, use *%APPDATA%/ccache/ccache.conf* (Windows),
-   *$HOME/Library/Preferences/ccache/ccache.conf (macOS) or
+   *$HOME/Library/Preferences/ccache/ccache.conf* (macOS) or
    *$HOME/.config/ccache/ccache.conf* (other systems).
 
 
@@ -383,6 +391,9 @@ project2 will be a different absolute path.
     See also <<_location_of_the_primary_configuration_file,LOCATION OF THE
     PRIMARY CONFIGURATION FILE>>.
 
+    If you want to use another *CCACHE_DIR* value temporarily for one ccache
+    invocation you can use the `-d/--directory` command line option instead.
+
 [[config_compiler]] *compiler* (*CCACHE_COMPILER* or (deprecated) *CCACHE_CC*)::
 
     This option can be used to force the name of the compiler to use. If set to
@@ -449,6 +460,29 @@ WRAPPERS>>.
 --
 --
 
+[[config_compiler_type]] *compiler_type* (*CCACHE_COMPILERTYPE*)::
+
+    Ccache normally guesses the compiler type based on the compiler name. The
+    *compiler_type* option lets you force a compiler type. This can be useful
+    if the compiler has a non-standard name but is actually one of the known
+    compiler types. Possible values are:
++
+--
+*auto*::
+    Guess one of the types below based on the compiler name (following
+    symlinks). This is the default.
+*clang*::
+    Clang-based compiler.
+*gcc*::
+    GCC-based compiler.
+*nvcc*::
+    NVCC (CUDA) compiler.
+*other::
+    Any compiler other than the known types.
+*pump*::
+    distcc's "pump" script.
+--
+
 [[config_compression]] *compression* (*CCACHE_COMPRESS* or *CCACHE_NOCOMPRESS*, see <<_boolean_values,Boolean values>> above)::
 
     If true, ccache will compress data it puts in the cache. However, this
index d9eba80..d3e4a9d 100644 (file)
@@ -1,6 +1,95 @@
 Ccache news
 ===========
 
+Ccache 4.1
+----------
+Release date: 2020-11-22
+
+New features
+~~~~~~~~~~~~
+
+- Symlinks are now followed when guessing the compiler. This makes ccache able
+  to guess compiler type “GCC” for a common symlink chain like this:
+  `/usr/bin/cc` → `/etc/alternatives/cc` → `/usr/bin/gcc` → `gcc-9` →
+  `x86_64-linux-gnu-gcc-9`.
+
+- Added a new `compiler_type` (`CCACHE_COMPILERTYPE`) configuration option that
+  allows for overriding the guessed compiler type.
+
+- Added support for caching compilations with `-fsyntax-only`.
+
+- Added a command line option `--config-path`, which specifies the
+  configuration file to operate on. It can be used instead of setting
+  `CCACHE_CONFIGPATH` temporarily.
+
+
+Bug fixes
+~~~~~~~~~
+
+- The original color diagnostics options are now retained when forcing colored
+  output. This fixes a bug where feature detection of the `-fcolor-diagnostics`
+  option would succeed when run via ccache even though the actual compiler
+  doesn’t support it (e.g. GCC <4.9).
+
+- Fixed a bug related to umask when using the `umask` (`CCACHE_UMASK`)
+  configuration option.
+
+- Allow `ccache ccache compiler ...` (repeated `ccache`) again.
+
+- Fixed parsing of dependency file in the “depend mode” so that filenames with
+  space or other special characters are handled correctly.
+
+- Fixed rewriting of the dependency file content when the object filename
+  includes space or other special characters.
+
+- Fixed runtime detection of AVX2 support, not relying on the sometimes broken
+  `__builtin_cpu_support` routine.
+
+- Added missing parameters to a log call, thus avoiding a crash when it is
+  found out at runtime that file cloning is unsupported by the OS.
+
+
+Portability and build fixes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- The ccache binary is now linked with `libatomic` if needed. This fixes build
+  problems with GCC on ARM and PowerPC.
+
+- Fixed build of BLAKE3 code with Clang 3.4 and 3.5.
+
+- Fixed “use of undeclared identifier 'CLONE_NOOWNERCOPY'” build error on macOS
+  10.12.
+
+- Fixed build problems related to missing AVX2 and AVX512 support on older
+  macOS versions.
+
+- Fixed static linkage with libgcc and libstdc++ for MinGW and made it
+  optional.
+
+- Fixed conditional compilation of “robust mutex” code for the inode cache
+  routines.
+
+- Fixed badly named man page filename (`Ccache.1` instead of `ccache.1`).
+
+- Disabled some tests on ancient Clang versions.
+
+
+Other improvements and fixes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- The man page is now built by default if the required tools are available.
+
+- Use CMake `A2X_EXE` variable instead of hardcoded `a2x`.
+
+- Improved build errors when building ccache with very old compiler versions.
+
+- Fall back to version “unknown” when Git is not installed.
+
+- Documented the relationship between `CCACHE_DIR` and `-d/--directory`.
+
+- Fixed incorrect reference and bad markup in the manual.
+
+
 Ccache 4.0
 ----------
 Release date: 2020-10-18
index 2b2c297..5b3d1db 100644 (file)
@@ -6,10 +6,13 @@ RUN apt-get update \
         bash \
         build-essential \
         ccache \
-        clang \
+        clang-3.4 \
         curl \
+        docbook-xml \
+        docbook-xsl \
         elfutils \
-        gcc-multilib \
+        g++-multilib \
+        ninja-build \
         wget \
         xsltproc \
  && rm -rf /var/lib/apt/lists/*
index a7f096c..7c9ddbc 100644 (file)
@@ -8,6 +8,8 @@ RUN apt-get update \
         ccache \
         clang \
         cmake \
+        docbook-xml \
+        docbook-xsl \
         elfutils \
         gcc-multilib \
         libzstd1-dev \
diff --git a/dockerfiles/ubuntu-18.04/Dockerfile b/dockerfiles/ubuntu-18.04/Dockerfile
new file mode 100644 (file)
index 0000000..4ab985b
--- /dev/null
@@ -0,0 +1,20 @@
+FROM ubuntu:18.04
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+        asciidoc \
+        bash \
+        build-essential \
+        ccache \
+        clang \
+        cmake \
+        docbook-xml \
+        docbook-xsl \
+        elfutils \
+        gcc-multilib \
+        libzstd-dev \
+        xsltproc \
+ && rm -rf /var/lib/apt/lists/*
+
+# Redirect all compilers to ccache.
+RUN for t in gcc g++ cc c++ clang clang++; do ln -vs /usr/bin/ccache /usr/local/bin/$t; done
index c5ec49c..f2be356 100644 (file)
@@ -9,6 +9,8 @@ RUN apt-get update \
         ccache \
         clang \
         cmake \
+        docbook-xml \
+        docbook-xsl \
         elfutils \
         gcc-multilib \
         libzstd-dev \
index 2dc0da9..a95c392 100755 (executable)
@@ -32,6 +32,9 @@ build ubuntu-14.04 gcc   g++     clang   -DZSTD_FROM_INTERNET=ON
 build ubuntu-16.04 gcc   g++     gcc
 build ubuntu-16.04 clang clang++ clang
 
+build ubuntu-18.04 gcc   g++     gcc
+build ubuntu-18.04 clang clang++ clang
+
 build ubuntu-20.04 gcc   g++     gcc
 build ubuntu-20.04 clang clang++ clang
 
index c240850..0f3fa75 100644 (file)
@@ -65,13 +65,13 @@ CheckOptions:
   - key:             readability-function-size.LineThreshold
     value:           700
   - key:             readability-function-size.StatementThreshold
-    value:           500
+    value:           999999
   - key:             readability-function-size.BranchThreshold
     value:           170
   - key:             readability-function-size.ParameterThreshold
     value:           6
   - key:             readability-function-size.NestingThreshold
-    value:           6
+    value:           999999
   - key:             readability-function-size.VariableThreshold
     value:           80
 ...
index 92a1e41..5270989 100644 (file)
@@ -156,6 +156,15 @@ Args::to_string() const
 }
 
 void
+Args::erase_last(string_view arg)
+{
+  const auto it = std::find(m_args.rbegin(), m_args.rend(), arg);
+  if (it != m_args.rend()) {
+    m_args.erase(std::next(it).base());
+  }
+}
+
+void
 Args::erase_with_prefix(string_view prefix)
 {
   m_args.erase(std::remove_if(m_args.begin(),
index be917ec..ae42809 100644 (file)
@@ -60,6 +60,9 @@ public:
   // in arguments is performed.
   std::string to_string() const;
 
+  // Remove last argument equal to `arg`, if any.
+  void erase_last(nonstd::string_view arg);
+
   // Remove all arguments with prefix `prefix`.
   void erase_with_prefix(nonstd::string_view prefix);
 
index e79b1c9..4d9eaf3 100644 (file)
@@ -31,6 +31,11 @@ struct ArgsInfo
   // The source file.
   std::string input_file;
 
+  // In normal compiler operation an output file is created if there is no
+  // compiler error. However certain flags like -fsyntax-only change this
+  // behavior.
+  bool expect_output_obj = true;
+
   // The output file being compiled to.
   std::string output_obj;
 
index 1225887..a88efc9 100644 (file)
@@ -11,6 +11,7 @@ set(
   Context.cpp
   Counters.cpp
   Decompressor.cpp
+  Depfile.cpp
   Hash.cpp
   Lockfile.cpp
   Logging.cpp
@@ -56,13 +57,17 @@ if(WIN32)
   target_link_libraries(ccache_lib PRIVATE ws2_32 "psapi")
 
   if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
-    target_link_libraries(
-      ccache_lib PRIVATE -static gcc stdc++ winpthread -dynamic)
-  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+    if(STATIC_LINK)
+      target_link_libraries(ccache_lib PRIVATE -static-libgcc -static-libstdc++ -static winpthread -dynamic)
+    else()
+      target_link_libraries(ccache_lib PRIVATE winpthread)
+    endif()
+  elseif(STATIC_LINK AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
     target_link_libraries(ccache_lib PRIVATE -static c++ -dynamic)
   endif()
 endif()
 
+set(THREADS_PREFER_PTHREAD_FLAG ON)
 find_package(Threads REQUIRED)
 target_link_libraries(
   ccache_lib
index 5879b51..c6964ad 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "Compressor.hpp"
 #include "exceptions.hpp"
+#include "fmtmacros.hpp"
 
 #include "third_party/fmt/core.h"
 
@@ -57,13 +58,13 @@ CacheEntryReader::CacheEntryReader(FILE* stream,
 void
 CacheEntryReader::dump_header(FILE* dump_stream)
 {
-  fmt::print(dump_stream, "Magic: {:.4}\n", m_magic);
-  fmt::print(dump_stream, "Version: {}\n", m_version);
-  fmt::print(dump_stream,
-             "Compression type: {}\n",
-             Compression::type_to_string(m_compression_type));
-  fmt::print(dump_stream, "Compression level: {}\n", m_compression_level);
-  fmt::print(dump_stream, "Content size: {}\n", m_content_size);
+  PRINT(dump_stream, "Magic: {:.4}\n", m_magic);
+  PRINT(dump_stream, "Version: {}\n", m_version);
+  PRINT(dump_stream,
+        "Compression type: {}\n",
+        Compression::type_to_string(m_compression_type));
+  PRINT(dump_stream, "Compression level: {}\n", m_compression_level);
+  PRINT(dump_stream, "Content size: {}\n", m_content_size);
 }
 
 void
index c7168e0..a2e09ca 100644 (file)
@@ -24,6 +24,7 @@
 #include "assertions.hpp"
 #include "ccache.hpp"
 #include "exceptions.hpp"
+#include "fmtmacros.hpp"
 
 #include "third_party/fmt/core.h"
 
@@ -46,6 +47,7 @@ enum class ConfigItem {
   cache_dir,
   compiler,
   compiler_check,
+  compiler_type,
   compression,
   compression_level,
   cpp_extension,
@@ -85,6 +87,7 @@ const std::unordered_map<std::string, ConfigItem> k_config_key_table = {
   {"cache_dir", ConfigItem::cache_dir},
   {"compiler", ConfigItem::compiler},
   {"compiler_check", ConfigItem::compiler_check},
+  {"compiler_type", ConfigItem::compiler_type},
   {"compression", ConfigItem::compression},
   {"compression_level", ConfigItem::compression_level},
   {"cpp_extension", ConfigItem::cpp_extension},
@@ -125,6 +128,7 @@ const std::unordered_map<std::string, std::string> k_env_variable_table = {
   {"COMMENTS", "keep_comments_cpp"},
   {"COMPILER", "compiler"},
   {"COMPILERCHECK", "compiler_check"},
+  {"COMPILERTYPE", "compiler_type"},
   {"COMPRESS", "compression"},
   {"COMPRESSLEVEL", "compression_level"},
   {"CPP2", "run_second_cpp"},
@@ -219,6 +223,25 @@ format_cache_size(uint64_t value)
   return Util::format_parsable_size_with_suffix(value);
 }
 
+CompilerType
+parse_compiler_type(const std::string& value)
+{
+  if (value == "clang") {
+    return CompilerType::clang;
+  } else if (value == "gcc") {
+    return CompilerType::gcc;
+  } else if (value == "nvcc") {
+    return CompilerType::nvcc;
+  } else if (value == "other") {
+    return CompilerType::other;
+  } else if (value == "pump") {
+    return CompilerType::pump;
+  } else {
+    // Allow any unknown value for forward compatibility.
+    return CompilerType::auto_guess;
+  }
+}
+
 uint32_t
 parse_sloppiness(const std::string& value)
 {
@@ -317,7 +340,7 @@ format_umask(uint32_t umask)
   if (umask == std::numeric_limits<uint32_t>::max()) {
     return {};
   } else {
-    return fmt::format("{:03o}", umask);
+    return FMT("{:03o}", umask);
   }
 }
 
@@ -389,6 +412,28 @@ parse_config_file(const std::string& path,
 
 } // namespace
 
+std::string
+compiler_type_to_string(CompilerType compiler_type)
+{
+#define CASE(type)                                                             \
+  case CompilerType::type:                                                     \
+    return #type
+
+  switch (compiler_type) {
+  case CompilerType::auto_guess:
+    return "auto";
+
+    CASE(clang);
+    CASE(gcc);
+    CASE(nvcc);
+    CASE(other);
+    CASE(pump);
+  }
+#undef CASE
+
+  ASSERT(false);
+}
+
 const std::string&
 Config::primary_config_path() const
 {
@@ -486,11 +531,14 @@ Config::get_string_value(const std::string& key) const
   case ConfigItem::compiler_check:
     return m_compiler_check;
 
+  case ConfigItem::compiler_type:
+    return compiler_type_to_string(m_compiler_type);
+
   case ConfigItem::compression:
     return format_bool(m_compression);
 
   case ConfigItem::compression_level:
-    return fmt::format("{}", m_compression_level);
+    return FMT("{}", m_compression_level);
 
   case ConfigItem::cpp_extension:
     return m_cpp_extension;
@@ -532,13 +580,13 @@ Config::get_string_value(const std::string& key) const
     return format_bool(m_keep_comments_cpp);
 
   case ConfigItem::limit_multiple:
-    return fmt::format("{:.1f}", m_limit_multiple);
+    return FMT("{:.1f}", m_limit_multiple);
 
   case ConfigItem::log_file:
     return m_log_file;
 
   case ConfigItem::max_files:
-    return fmt::format("{}", m_max_files);
+    return FMT("{}", m_max_files);
 
   case ConfigItem::max_size:
     return format_cache_size(m_max_size);
@@ -615,17 +663,17 @@ Config::set_value_in_file(const std::string& path,
                              const std::string& c_key,
                              const std::string& /*c_value*/) {
                            if (c_key == key) {
-                             output.write(fmt::format("{} = {}\n", key, value));
+                             output.write(FMT("{} = {}\n", key, value));
                              found = true;
                            } else {
-                             output.write(fmt::format("{}\n", c_line));
+                             output.write(FMT("{}\n", c_line));
                            }
                          })) {
     throw Error("failed to open {}: {}", path, strerror(errno));
   }
 
   if (!found) {
-    output.write(fmt::format("{} = {}\n", key, value));
+    output.write(FMT("{} = {}\n", key, value));
   }
 
   output.commit();
@@ -686,6 +734,10 @@ Config::set_item(const std::string& key,
     m_compiler_check = value;
     break;
 
+  case ConfigItem::compiler_type:
+    m_compiler_type = parse_compiler_type(value);
+    break;
+
   case ConfigItem::compression:
     m_compression = parse_bool(value, env_var_key, negate);
     break;
@@ -749,7 +801,7 @@ Config::set_item(const std::string& key,
     break;
 
   case ConfigItem::limit_multiple:
-    m_limit_multiple = parse_double(value);
+    m_limit_multiple = Util::clamp(parse_double(value), 0.0, 1.0);
     break;
 
   case ConfigItem::log_file:
@@ -834,7 +886,7 @@ std::string
 Config::default_temporary_dir(const std::string& cache_dir)
 {
 #ifdef HAVE_GETEUID
-  std::string user_tmp_dir = fmt::format("/run/user/{}", geteuid());
+  std::string user_tmp_dir = FMT("/run/user/{}", geteuid());
   if (Stat::stat(user_tmp_dir).is_directory()) {
     return user_tmp_dir + "/ccache-tmp";
   }
index bc8bb4f..a5945fa 100644 (file)
@@ -30,7 +30,9 @@
 #include <string>
 #include <unordered_map>
 
-class Config;
+enum class CompilerType { auto_guess, clang, gcc, nvcc, other, pump };
+
+std::string compiler_type_to_string(CompilerType compiler_type);
 
 class Config
 {
@@ -44,6 +46,7 @@ public:
   const std::string& cache_dir() const;
   const std::string& compiler() const;
   const std::string& compiler_check() const;
+  CompilerType compiler_type() const;
   bool compression() const;
   int8_t compression_level() const;
   const std::string& cpp_extension() const;
@@ -80,12 +83,12 @@ public:
   void set_cache_dir(const std::string& value);
   void set_cpp_extension(const std::string& value);
   void set_compiler(const std::string& value);
+  void set_compiler_type(CompilerType compiler_type);
   void set_depend_mode(bool value);
   void set_debug(bool value);
   void set_direct_mode(bool value);
   void set_ignore_options(const std::string& value);
   void set_inode_cache(bool value);
-  void set_limit_multiple(double value);
   void set_max_files(uint64_t value);
   void set_max_size(uint64_t value);
   void set_run_second_cpp(bool value);
@@ -134,6 +137,7 @@ private:
   std::string m_cache_dir;
   std::string m_compiler = "";
   std::string m_compiler_check = "mtime";
+  CompilerType m_compiler_type = CompilerType::auto_guess;
   bool m_compression = true;
   int8_t m_compression_level = 0; // Use default level
   std::string m_cpp_extension = "";
@@ -209,6 +213,12 @@ Config::compiler_check() const
   return m_compiler_check;
 }
 
+inline CompilerType
+Config::compiler_type() const
+{
+  return m_compiler_type;
+}
+
 inline bool
 Config::compression() const
 {
@@ -423,6 +433,12 @@ Config::set_compiler(const std::string& value)
 }
 
 inline void
+Config::set_compiler_type(CompilerType value)
+{
+  m_compiler_type = value;
+}
+
+inline void
 Config::set_depend_mode(bool value)
 {
   m_depend_mode = value;
@@ -453,12 +469,6 @@ Config::set_inode_cache(bool value)
 }
 
 inline void
-Config::set_limit_multiple(double value)
-{
-  m_limit_multiple = value;
-}
-
-inline void
 Config::set_max_files(uint64_t value)
 {
   m_max_files = value;
index bb63946..7706b7d 100644 (file)
@@ -28,7 +28,6 @@
 #include <string>
 #include <vector>
 
-using Logging::log;
 using nonstd::string_view;
 
 Context::Context()
@@ -83,7 +82,7 @@ Context::set_ignore_options(const std::vector<std::string>& options)
     if (n_wildcards == 0 || (n_wildcards == 1 && option.back() == '*')) {
       m_ignore_options.push_back(option);
     } else {
-      log("Skipping malformed ignore_options item: {}", option);
+      LOG("Skipping malformed ignore_options item: {}", option);
       continue;
     }
   }
index 239081f..7af0705 100644 (file)
@@ -93,10 +93,6 @@ public:
   // The name of the cpp stderr file.
   std::string cpp_stderr;
 
-  // Compiler guessing is currently only based on the compiler name, so nothing
-  // should hard-depend on it if possible.
-  GuessedCompiler guessed_compiler = GuessedCompiler::unknown;
-
   // The .gch/.pch/.pth file used for compilation.
   std::string included_pch_file;
 
diff --git a/src/Depfile.cpp b/src/Depfile.cpp
new file mode 100644 (file)
index 0000000..ed491f4
--- /dev/null
@@ -0,0 +1,208 @@
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "Depfile.hpp"
+
+#include "Context.hpp"
+#include "Hash.hpp"
+#include "Logging.hpp"
+#include "assertions.hpp"
+
+static inline bool
+is_blank(const std::string& s)
+{
+  return std::all_of(s.begin(), s.end(), [](char c) { return isspace(c); });
+}
+
+namespace Depfile {
+
+std::string
+escape_filename(nonstd::string_view filename)
+{
+  std::string result;
+  result.reserve(filename.size());
+  for (const char c : filename) {
+    switch (c) {
+    case '\\':
+    case '#':
+    case ':':
+    case ' ':
+    case '\t':
+      result.push_back('\\');
+      break;
+    case '$':
+      result.push_back('$');
+      break;
+    }
+    result.push_back(c);
+  }
+  return result;
+}
+
+nonstd::optional<std::string>
+rewrite_paths(const Context& ctx, const std::string& file_content)
+{
+  ASSERT(!ctx.config.base_dir().empty());
+  ASSERT(ctx.has_absolute_include_headers);
+
+  // Fast path for the common case:
+  if (file_content.find(ctx.config.base_dir()) == std::string::npos) {
+    return nonstd::nullopt;
+  }
+
+  std::string adjusted_file_content;
+  adjusted_file_content.reserve(file_content.size());
+
+  bool content_rewritten = false;
+  for (const auto& line : Util::split_into_views(file_content, "\n")) {
+    const auto tokens = Util::split_into_views(line, " \t");
+    for (size_t i = 0; i < tokens.size(); ++i) {
+      DEBUG_ASSERT(line.length() > 0); // line.empty() -> no tokens
+      if (i > 0 || line[0] == ' ' || line[0] == '\t') {
+        adjusted_file_content.push_back(' ');
+      }
+
+      const auto& token = tokens[i];
+      bool token_rewritten = false;
+      if (Util::is_absolute_path(token)) {
+        const auto new_path = Util::make_relative_path(ctx, token);
+        if (new_path != token) {
+          adjusted_file_content.append(new_path);
+          token_rewritten = true;
+        }
+      }
+      if (token_rewritten) {
+        content_rewritten = true;
+      } else {
+        adjusted_file_content.append(token.begin(), token.end());
+      }
+    }
+    adjusted_file_content.push_back('\n');
+  }
+
+  if (content_rewritten) {
+    return adjusted_file_content;
+  } else {
+    return nonstd::nullopt;
+  }
+}
+
+// Replace absolute paths with relative paths in the provided dependency file.
+void
+make_paths_relative_in_output_dep(const Context& ctx)
+{
+  if (ctx.config.base_dir().empty()) {
+    LOG_RAW("Base dir not set, skip using relative paths");
+    return; // nothing to do
+  }
+  if (!ctx.has_absolute_include_headers) {
+    LOG_RAW(
+      "No absolute path for included files found, skip using relative paths");
+    return; // nothing to do
+  }
+
+  const std::string& output_dep = ctx.args_info.output_dep;
+  std::string file_content;
+  try {
+    file_content = Util::read_file(output_dep);
+  } catch (const Error& e) {
+    LOG("Cannot open dependency file {}: {}", output_dep, e.what());
+    return;
+  }
+  const auto new_content = rewrite_paths(ctx, file_content);
+  if (new_content) {
+    Util::write_file(output_dep, *new_content);
+  } else {
+    LOG("No paths in dependency file {} made relative", output_dep);
+  }
+}
+
+std::vector<std::string>
+tokenize(nonstd::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.
+
+  std::vector<std::string> result;
+  const size_t length = file_content.size();
+  std::string token;
+  size_t p = 0;
+
+  while (p < length) {
+    // Each token is separated by whitespace.
+    if (isspace(file_content[p])) {
+      while (p < length && isspace(file_content[p])) {
+        ++p;
+      }
+      if (!is_blank(token)) {
+        result.push_back(token);
+      }
+      token.clear();
+      continue;
+    }
+
+    char c = file_content[p];
+    switch (c) {
+    case '\\':
+      if (p + 1 < length) {
+        const char next = file_content[p + 1];
+        switch (next) {
+        // A backspace followed by any of the below characters leaves the
+        // character as is.
+        case '\\':
+        case '#':
+        case ':':
+        case ' ':
+        case '\t':
+          c = next;
+          ++p;
+          break;
+        // Backslash followed by newline is interpreted like a space, so simply
+        // the backslash.
+        case '\n':
+          ++p;
+          continue;
+        }
+      }
+      break;
+    case '$':
+      if (p + 1 < length) {
+        const char next = file_content[p + 1];
+        switch (next) {
+        // A dollar sign preceded by a dollar sign escapes the dollar sign.
+        case '$':
+          c = next;
+          ++p;
+          break;
+        }
+      }
+      break;
+    }
+
+    token.push_back(c);
+    ++p;
+  }
+
+  if (!is_blank(token)) {
+    result.push_back(token);
+  }
+
+  return result;
+}
+
+} // namespace Depfile
diff --git a/src/Depfile.hpp b/src/Depfile.hpp
new file mode 100644 (file)
index 0000000..770f789
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+class Context;
+class Hash;
+
+#include "Digest.hpp"
+
+#include "third_party/nonstd/optional.hpp"
+#include "third_party/nonstd/string_view.hpp"
+
+#include <string>
+#include <vector>
+
+namespace Depfile {
+
+std::string escape_filename(nonstd::string_view filename);
+nonstd::optional<std::string> rewrite_paths(const Context& ctx,
+                                            const std::string& file_content);
+void make_paths_relative_in_output_dep(const Context& ctx);
+std::vector<std::string> tokenize(nonstd::string_view file_content);
+
+} // namespace Depfile
index bb186b7..ccc6f7b 100644 (file)
@@ -20,8 +20,8 @@
 
 #include "Fd.hpp"
 #include "Logging.hpp"
+#include "fmtmacros.hpp"
 
-using Logging::log;
 using nonstd::string_view;
 
 const string_view HASH_DELIMITER("\000cCaChE\000", 8);
@@ -98,7 +98,7 @@ Hash&
 Hash::hash(int64_t x)
 {
   hash_buffer(string_view(reinterpret_cast<const char*>(&x), sizeof(x)));
-  add_debug_text(fmt::format("{}\n", x));
+  add_debug_text(FMT("{}\n", x));
   return *this;
 }
 
@@ -114,7 +114,7 @@ Hash::hash_file(const std::string& path)
 {
   Fd fd(open(path.c_str(), O_RDONLY | O_BINARY));
   if (!fd) {
-    log("Failed to open {}: {}", path, strerror(errno));
+    LOG("Failed to open {}: {}", path, strerror(errno));
     return false;
   }
 
index eeef9ac..a0e97a1 100644 (file)
 #include "Stat.hpp"
 #include "TemporaryFile.hpp"
 #include "Util.hpp"
+#include "fmtmacros.hpp"
 
 #include <atomic>
 #include <libgen.h>
 #include <sys/mman.h>
 #include <type_traits>
 
-using Logging::log;
-
 // The inode cache resides on a file that is mapped into shared memory by
 // running processes. It is implemented as a two level structure, where the top
 // level is a hash table consisting of buckets. Each bucket contains entries
@@ -132,12 +131,12 @@ InodeCache::mmap_file(const std::string& inode_cache_file)
   }
   Fd fd(open(inode_cache_file.c_str(), O_RDWR));
   if (!fd) {
-    log("Failed to open inode cache {}: {}", inode_cache_file, strerror(errno));
+    LOG("Failed to open inode cache {}: {}", inode_cache_file, strerror(errno));
     return false;
   }
   bool is_nfs;
   if (Util::is_nfs_fd(*fd, &is_nfs) == 0 && is_nfs) {
-    log(
+    LOG(
       "Inode cache not supported because the cache file is located on nfs: {}",
       inode_cache_file);
     return false;
@@ -146,13 +145,13 @@ InodeCache::mmap_file(const std::string& inode_cache_file)
     nullptr, sizeof(SharedRegion), PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0));
   fd.close();
   if (sr == reinterpret_cast<void*>(-1)) {
-    log("Failed to mmap {}: {}", inode_cache_file, strerror(errno));
+    LOG("Failed to mmap {}: {}", inode_cache_file, strerror(errno));
     return false;
   }
   // Drop the file from disk if the found version is not matching. This will
   // allow a new file to be generated.
   if (sr->version != k_version) {
-    log(
+    LOG(
       "Dropping inode cache because found version {} does not match expected"
       " version {}",
       sr->version,
@@ -163,7 +162,7 @@ InodeCache::mmap_file(const std::string& inode_cache_file)
   }
   m_sr = sr;
   if (m_config.debug()) {
-    log("inode cache file loaded: {}", inode_cache_file);
+    LOG("inode cache file loaded: {}", inode_cache_file);
   }
   return true;
 }
@@ -175,7 +174,7 @@ InodeCache::hash_inode(const std::string& path,
 {
   Stat stat = Stat::stat(path);
   if (!stat) {
-    log("Could not stat {}: {}", path, strerror(stat.error_number()));
+    LOG("Could not stat {}: {}", path, strerror(stat.error_number()));
     return false;
   }
 
@@ -208,29 +207,29 @@ InodeCache::acquire_bucket(uint32_t index)
 {
   Bucket* bucket = &m_sr->buckets[index];
   int err = pthread_mutex_lock(&bucket->mt);
-#ifdef PTHREAD_MUTEX_ROBUST
+#ifdef HAVE_PTHREAD_MUTEX_ROBUST
   if (err == EOWNERDEAD) {
     if (m_config.debug()) {
       ++m_sr->errors;
     }
     err = pthread_mutex_consistent(&bucket->mt);
     if (err) {
-      log(
+      LOG(
         "Can't consolidate stale mutex at index {}: {}", index, strerror(err));
-      log("Consider removing the inode cache file if the problem persists");
+      LOG_RAW("Consider removing the inode cache file if the problem persists");
       return nullptr;
     }
-    log("Wiping bucket at index {} because of stale mutex", index);
+    LOG("Wiping bucket at index {} because of stale mutex", index);
     memset(bucket->entries, 0, sizeof(Bucket::entries));
   } else {
 #endif
-    if (err) {
-      log("Failed to lock mutex at index {}: {}", index, strerror(err));
-      log("Consider removing the inode cache file if problem persists");
+    if (err != 0) {
+      LOG("Failed to lock mutex at index {}: {}", index, strerror(err));
+      LOG_RAW("Consider removing the inode cache file if problem persists");
       ++m_sr->errors;
       return nullptr;
     }
-#ifdef PTHREAD_MUTEX_ROBUST
+#ifdef HAVE_PTHREAD_MUTEX_ROBUST
   }
 #endif
   return bucket;
@@ -253,7 +252,7 @@ InodeCache::release_bucket(Bucket* bucket)
 bool
 InodeCache::create_new_file(const std::string& filename)
 {
-  log("Creating a new inode cache");
+  LOG_RAW("Creating a new inode cache");
 
   // Create the new file to a temporary name to prevent other processes from
   // mapping it before it is fully initialized.
@@ -263,7 +262,7 @@ InodeCache::create_new_file(const std::string& filename)
 
   bool is_nfs;
   if (Util::is_nfs_fd(*tmp_file.fd, &is_nfs) == 0 && is_nfs) {
-    log(
+    LOG(
       "Inode cache not supported because the cache file would be located on"
       " nfs: {}",
       filename);
@@ -271,7 +270,7 @@ InodeCache::create_new_file(const std::string& filename)
   }
   int err = Util::fallocate(*tmp_file.fd, sizeof(SharedRegion));
   if (err) {
-    log("Failed to allocate file space for inode cache: {}", strerror(err));
+    LOG("Failed to allocate file space for inode cache: {}", strerror(err));
     return false;
   }
   SharedRegion* sr =
@@ -282,7 +281,7 @@ InodeCache::create_new_file(const std::string& filename)
                                          *tmp_file.fd,
                                          0));
   if (sr == reinterpret_cast<void*>(-1)) {
-    log("Failed to mmap new inode cache: {}", strerror(errno));
+    LOG("Failed to mmap new inode cache: {}", strerror(errno));
     return false;
   }
 
@@ -291,7 +290,7 @@ InodeCache::create_new_file(const std::string& filename)
   pthread_mutexattr_t mattr;
   pthread_mutexattr_init(&mattr);
   pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
-#ifdef PTHREAD_MUTEX_ROBUST
+#ifdef HAVE_PTHREAD_MUTEX_ROBUST
   pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
 #endif
   for (auto& bucket : sr->buckets) {
@@ -307,7 +306,7 @@ InodeCache::create_new_file(const std::string& filename)
   // which will make us use the first created file even if we didn't win the
   // race.
   if (link(tmp_file.path.c_str(), filename.c_str()) != 0) {
-    log("Failed to link new inode cache: {}", strerror(errno));
+    LOG("Failed to link new inode cache: {}", strerror(errno));
     return false;
   }
 
@@ -397,7 +396,7 @@ InodeCache::get(const std::string& path,
   }
   release_bucket(bucket);
 
-  log("inode cache {}: {}", found ? "hit" : "miss", path);
+  LOG("inode cache {}: {}", found ? "hit" : "miss", path);
 
   if (m_config.debug()) {
     if (found) {
@@ -405,7 +404,7 @@ InodeCache::get(const std::string& path,
     } else {
       ++m_sr->misses;
     }
-    log("accumulated stats for inode cache: hits={}, misses={}, errors={}",
+    LOG("accumulated stats for inode cache: hits={}, misses={}, errors={}",
         m_sr->hits.load(),
         m_sr->misses.load(),
         m_sr->errors.load());
@@ -444,7 +443,7 @@ InodeCache::put(const std::string& path,
 
   release_bucket(bucket);
 
-  log("inode cache insert: {}", path);
+  LOG("inode cache insert: {}", path);
 
   return true;
 }
@@ -466,7 +465,7 @@ InodeCache::drop()
 std::string
 InodeCache::get_file()
 {
-  return fmt::format("{}/inode-cache.v{}", m_config.temporary_dir(), k_version);
+  return FMT("{}/inode-cache.v{}", m_config.temporary_dir(), k_version);
 }
 
 int64_t
index cd72900..715ffa4 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "Logging.hpp"
 #include "Util.hpp"
+#include "fmtmacros.hpp"
 
 #ifdef _WIN32
 #  include "Win32Util.hpp"
@@ -31,8 +32,6 @@
 #include <sstream>
 #include <thread>
 
-using Logging::log;
-
 namespace {
 
 #ifndef _WIN32
@@ -51,7 +50,7 @@ do_acquire_posix(const std::string& lockfile, uint32_t staleness_limit)
   const auto content_prefix = ss.str();
 
   while (true) {
-    auto my_content = fmt::format("{}:{}", content_prefix, time(nullptr));
+    auto my_content = FMT("{}:{}", content_prefix, time(nullptr));
 
     if (symlink(my_content.c_str(), lockfile.c_str()) == 0) {
       // We got the lock.
@@ -59,7 +58,7 @@ do_acquire_posix(const std::string& lockfile, uint32_t staleness_limit)
     }
 
     int saved_errno = errno;
-    log("lockfile_acquire: symlink {}: {}", lockfile, strerror(saved_errno));
+    LOG("lockfile_acquire: symlink {}: {}", lockfile, strerror(saved_errno));
     if (saved_errno == ENOENT) {
       // Directory doesn't exist?
       if (Util::create_dir(Util::dir_name(lockfile))) {
@@ -86,41 +85,41 @@ do_acquire_posix(const std::string& lockfile, uint32_t staleness_limit)
         // acquiring it.
         continue;
       } else {
-        log("lockfile_acquire: readlink {}: {}", lockfile, strerror(errno));
+        LOG("lockfile_acquire: readlink {}: {}", lockfile, strerror(errno));
         return false;
       }
     }
 
     if (content == my_content) {
       // Lost NFS reply?
-      log("lockfile_acquire: symlink {} failed but we got the lock anyway",
+      LOG("lockfile_acquire: symlink {} failed but we got the lock anyway",
           lockfile);
       return true;
     }
 
     // A possible improvement here would be to check if the process holding the
     // lock is still alive and break the lock early if it isn't.
-    log("lockfile_acquire: lock info for {}: {}", lockfile, content);
+    LOG("lockfile_acquire: lock info for {}: {}", lockfile, content);
 
     if (initial_content.empty()) {
       initial_content = content;
     }
 
     if (slept <= staleness_limit) {
-      log("lockfile_acquire: failed to acquire {}; sleeping {} microseconds",
+      LOG("lockfile_acquire: failed to acquire {}; sleeping {} microseconds",
           lockfile,
           to_sleep);
       usleep(to_sleep);
       slept += to_sleep;
       to_sleep = std::min(max_to_sleep, 2 * to_sleep);
     } else if (content != initial_content) {
-      log("lockfile_acquire: gave up acquiring {}", lockfile);
+      LOG("lockfile_acquire: gave up acquiring {}", lockfile);
       return false;
     } else {
       // The lock seems to be stale -- break it and try again.
-      log("lockfile_acquire: breaking {}", lockfile);
+      LOG("lockfile_acquire: breaking {}", lockfile);
       if (!Util::unlink_tmp(lockfile)) {
-        log("Failed to unlink {}: {}", lockfile, strerror(errno));
+        LOG("Failed to unlink {}: {}", lockfile, strerror(errno));
         return false;
       }
       to_sleep = 1000;
@@ -155,7 +154,7 @@ do_acquire_win32(const std::string& lockfile, uint32_t staleness_limit)
     }
 
     DWORD error = GetLastError();
-    log("lockfile_acquire: CreateFile {}: {} ({})",
+    LOG("lockfile_acquire: CreateFile {}: {} ({})",
         lockfile,
         Win32Util::error_message(error),
         error);
@@ -175,11 +174,11 @@ do_acquire_win32(const std::string& lockfile, uint32_t staleness_limit)
     }
 
     if (slept > staleness_limit) {
-      log("lockfile_acquire: gave up acquiring {}", lockfile);
+      LOG("lockfile_acquire: gave up acquiring {}", lockfile);
       break;
     }
 
-    log("lockfile_acquire: failed to acquire {}; sleeping {} microseconds",
+    LOG("lockfile_acquire: failed to acquire {}; sleeping {} microseconds",
         lockfile,
         to_sleep);
     usleep(to_sleep);
@@ -203,19 +202,19 @@ Lockfile::Lockfile(const std::string& path, uint32_t staleness_limit)
   m_handle = do_acquire_win32(m_lockfile, staleness_limit);
 #endif
   if (acquired()) {
-    log("Acquired lock {}", m_lockfile);
+    LOG("Acquired lock {}", m_lockfile);
   } else {
-    log("Failed to acquire lock {}", m_lockfile);
+    LOG("Failed to acquire lock {}", m_lockfile);
   }
 }
 
 Lockfile::~Lockfile()
 {
   if (acquired()) {
-    log("Releasing lock {}", m_lockfile);
+    LOG("Releasing lock {}", m_lockfile);
 #ifndef _WIN32
     if (!Util::unlink_tmp(m_lockfile)) {
-      log("Failed to unlink {}: {}", m_lockfile, strerror(errno));
+      LOG("Failed to unlink {}: {}", m_lockfile, strerror(errno));
     }
 #else
     CloseHandle(m_handle);
index e2057b2..9a5d99b 100644 (file)
@@ -24,6 +24,7 @@
 #include "Util.hpp"
 #include "exceptions.hpp"
 #include "execute.hpp"
+#include "fmtmacros.hpp"
 
 #ifdef HAVE_SYSLOG_H
 #  include <syslog.h>
@@ -67,10 +68,10 @@ bool debug_log_enabled = false;
 print_fatal_error_and_exit()
 {
   // Note: Can't throw Fatal since that would lead to recursion.
-  fmt::print(stderr,
-             "ccache: error: Failed to write to {}: {}\n",
-             logfile_path,
-             strerror(errno));
+  PRINT(stderr,
+        "ccache: error: Failed to write to {}: {}\n",
+        logfile_path,
+        strerror(errno));
   exit(EXIT_FAILURE);
 }
 
@@ -182,7 +183,7 @@ dump_log(const std::string& path)
   if (file) {
     (void)fwrite(debug_log_buffer.data(), debug_log_buffer.length(), 1, *file);
   } else {
-    log("Failed to open {}: {}", path, strerror(errno));
+    LOG("Failed to open {}: {}", path, strerror(errno));
   }
 }
 
index 278812b..38553f2 100644 (file)
 #include "FormatNonstdStringView.hpp"
 
 #include "third_party/fmt/core.h"
+#include "third_party/fmt/format.h"
 #include "third_party/nonstd/optional.hpp"
 #include "third_party/nonstd/string_view.hpp"
 
 #include <string>
 #include <utility>
 
+// Log a raw message (plus a newline character).
+#define LOG_RAW(message_)                                                      \
+  do {                                                                         \
+    if (Logging::enabled()) {                                                  \
+      Logging::log(nonstd::string_view(message_));                             \
+    }                                                                          \
+  } while (false)
+
+// Log a message (plus a newline character) described by a format string with at
+// least one placeholder. `format` is compile-time checked if CMAKE_CXX_STANDARD
+// >= 14.
+#define LOG(format_, ...) LOG_RAW(fmt::format(FMT_STRING(format_), __VA_ARGS__))
+
+// Log a message (plus a newline character) described by a format string with at
+// least one placeholder without flushing and with a reused timestamp. `format`
+// is compile-time checked if CMAKE_CXX_STANDARD >= 14.
+#define BULK_LOG(format_, ...)                                                 \
+  do {                                                                         \
+    if (Logging::enabled()) {                                                  \
+      Logging::bulk_log(fmt::format(FMT_STRING(format_), __VA_ARGS__));        \
+    }                                                                          \
+  } while (false)
+
 class Config;
 
 namespace Logging {
@@ -50,28 +74,4 @@ void bulk_log(nonstd::string_view message);
 // Write the current log memory buffer `path`.
 void dump_log(const std::string& path);
 
-// Log a message (plus a newline character). `args` are forwarded to
-// `fmt::format`.
-template<typename... T>
-inline void
-log(T&&... args)
-{
-  if (!enabled()) {
-    return;
-  }
-  log(nonstd::string_view(fmt::format(std::forward<T>(args)...)));
-}
-
-// Log a message (plus a newline character) without flushing and with a reused
-// timestamp. `args` are forwarded to `fmt::format`.
-template<typename... T>
-inline void
-bulk_log(T&&... args)
-{
-  if (!enabled()) {
-    return;
-  }
-  bulk_log(nonstd::string_view(fmt::format(std::forward<T>(args)...)));
-}
-
 } // namespace Logging
index 64ceae1..9ee87dc 100644 (file)
@@ -30,6 +30,7 @@
 #include "Logging.hpp"
 #include "StdMakeUnique.hpp"
 #include "ccache.hpp"
+#include "fmtmacros.hpp"
 #include "hashutil.hpp"
 
 // Manifest data format
 // 1: Introduced in ccache 3.0. (Files are always compressed with gzip.)
 // 2: Introduced in ccache 4.0.
 
-using Logging::log;
 using nonstd::nullopt;
 using nonstd::optional;
 
@@ -167,6 +167,12 @@ struct ResultEntry
   Digest name;
 };
 
+bool
+operator==(const ResultEntry& lhs, const ResultEntry& rhs)
+{
+  return lhs.file_info_indexes == rhs.file_info_indexes && lhs.name == rhs.name;
+}
+
 struct ManifestData
 {
   // Referenced include files.
@@ -178,7 +184,7 @@ struct ManifestData
   // Result names plus references to include file infos.
   std::vector<ResultEntry> results;
 
-  void
+  bool
   add_result_entry(
     const Digest& result_digest,
     const std::unordered_map<std::string, Digest>& included_files,
@@ -207,7 +213,13 @@ struct ManifestData
                                                       save_timestamp));
     }
 
-    results.push_back(ResultEntry{std::move(file_info_indexes), result_digest});
+    ResultEntry entry{std::move(file_info_indexes), result_digest};
+    if (std::find(results.begin(), results.end(), entry) == results.end()) {
+      results.push_back(std::move(entry));
+      return true;
+    } else {
+      return false;
+    }
   }
 
 private:
@@ -423,28 +435,28 @@ verify_result(const Context& ctx,
 
     // Clang stores the mtime of the included files in the precompiled header,
     // and will error out if that header is later used without rebuilding.
-    if ((ctx.guessed_compiler == GuessedCompiler::clang
-         || ctx.guessed_compiler == GuessedCompiler::unknown)
+    if ((ctx.config.compiler_type() == CompilerType::clang
+         || ctx.config.compiler_type() == CompilerType::other)
         && ctx.args_info.output_is_precompiled_header
         && !ctx.args_info.fno_pch_timestamp && fi.mtime != fs.mtime) {
-      log("Precompiled header includes {}, which has a new mtime", path);
+      LOG("Precompiled header includes {}, which has a new mtime", path);
       return false;
     }
 
     if (ctx.config.sloppiness() & SLOPPY_FILE_STAT_MATCHES) {
       if (!(ctx.config.sloppiness() & SLOPPY_FILE_STAT_MATCHES_CTIME)) {
         if (fi.mtime == fs.mtime && fi.ctime == fs.ctime) {
-          log("mtime/ctime hit for {}", path);
+          LOG("mtime/ctime hit for {}", path);
           continue;
         } else {
-          log("mtime/ctime miss for {}", path);
+          LOG("mtime/ctime miss for {}", path);
         }
       } else {
         if (fi.mtime == fs.mtime) {
-          log("mtime hit for {}", path);
+          LOG("mtime hit for {}", path);
           continue;
         } else {
-          log("mtime miss for {}", path);
+          LOG("mtime miss for {}", path);
         }
       }
     }
@@ -454,7 +466,7 @@ verify_result(const Context& ctx,
       Hash hash;
       int ret = hash_source_code_file(ctx, hash, path, fs.size);
       if (ret & HASH_SOURCE_CODE_ERROR) {
-        log("Failed hashing {}", path);
+        LOG("Failed hashing {}", path);
         return false;
       }
       if (ret & HASH_SOURCE_CODE_FOUND_TIME) {
@@ -492,11 +504,11 @@ get(const Context& ctx, const std::string& path)
       // Update modification timestamp to save files from LRU cleanup.
       Util::update_mtime(path);
     } else {
-      log("No such manifest file");
+      LOG_RAW("No such manifest file");
       return nullopt;
     }
   } catch (const Error& e) {
-    log("Error: {}", e.what());
+    LOG("Error: {}", e.what());
     return nullopt;
   }
 
@@ -537,7 +549,7 @@ put(const Config& config,
       mf = std::make_unique<ManifestData>();
     }
   } catch (const Error& e) {
-    log("Error: {}", e.what());
+    LOG("Error: {}", e.what());
     // Manifest file was corrupt, ignore.
     mf = std::make_unique<ManifestData>();
   }
@@ -553,28 +565,30 @@ put(const Config& config,
     // A good way of solving this would be to maintain the result entries in
     // LRU order and discarding the old ones. An easy way is to throw away all
     // entries when there are too many. Let's do that for now.
-    log("More than {} entries in manifest file; discarding",
+    LOG("More than {} entries in manifest file; discarding",
         k_max_manifest_entries);
     mf = std::make_unique<ManifestData>();
   } else if (mf->file_infos.size() > k_max_manifest_file_info_entries) {
     // Rarely, FileInfo entries can grow large in pathological cases where
     // many included files change, but the main file does not. This also puts
     // an upper bound on the number of FileInfo entries.
-    log("More than {} FileInfo entries in manifest file; discarding",
+    LOG("More than {} FileInfo entries in manifest file; discarding",
         k_max_manifest_file_info_entries);
     mf = std::make_unique<ManifestData>();
   }
 
-  mf->add_result_entry(
+  bool added = mf->add_result_entry(
     result_name, included_files, time_of_compilation, save_timestamp);
 
-  try {
-    write_manifest(config, path, *mf);
-    return true;
-  } catch (const Error& e) {
-    log("Error: {}", e.what());
-    return false;
+  if (added) {
+    try {
+      write_manifest(config, path, *mf);
+      return true;
+    } catch (const Error& e) {
+      LOG("Error: {}", e.what());
+    }
   }
+  return false;
 }
 
 bool
@@ -584,37 +598,37 @@ dump(const std::string& path, FILE* stream)
   try {
     mf = read_manifest(path, stream);
   } catch (const Error& e) {
-    fmt::print(stream, "Error: {}\n", e.what());
+    PRINT(stream, "Error: {}\n", e.what());
     return false;
   }
 
   if (!mf) {
-    fmt::print(stream, "Error: No such file: {}\n", path);
+    PRINT(stream, "Error: No such file: {}\n", path);
     return false;
   }
 
-  fmt::print(stream, "File paths ({}):\n", mf->files.size());
+  PRINT(stream, "File paths ({}):\n", mf->files.size());
   for (size_t i = 0; i < mf->files.size(); ++i) {
-    fmt::print(stream, "  {}: {}\n", i, mf->files[i]);
+    PRINT(stream, "  {}: {}\n", i, mf->files[i]);
   }
-  fmt::print(stream, "File infos ({}):\n", mf->file_infos.size());
+  PRINT(stream, "File infos ({}):\n", mf->file_infos.size());
   for (size_t i = 0; i < mf->file_infos.size(); ++i) {
-    fmt::print(stream, "  {}:\n", i);
-    fmt::print(stream, "    Path index: {}\n", mf->file_infos[i].index);
-    fmt::print(stream, "    Hash: {}\n", mf->file_infos[i].digest.to_string());
-    fmt::print(stream, "    File size: {}\n", mf->file_infos[i].fsize);
-    fmt::print(stream, "    Mtime: {}\n", mf->file_infos[i].mtime);
-    fmt::print(stream, "    Ctime: {}\n", mf->file_infos[i].ctime);
+    PRINT(stream, "  {}:\n", i);
+    PRINT(stream, "    Path index: {}\n", mf->file_infos[i].index);
+    PRINT(stream, "    Hash: {}\n", mf->file_infos[i].digest.to_string());
+    PRINT(stream, "    File size: {}\n", mf->file_infos[i].fsize);
+    PRINT(stream, "    Mtime: {}\n", mf->file_infos[i].mtime);
+    PRINT(stream, "    Ctime: {}\n", mf->file_infos[i].ctime);
   }
-  fmt::print(stream, "Results ({}):\n", mf->results.size());
+  PRINT(stream, "Results ({}):\n", mf->results.size());
   for (size_t i = 0; i < mf->results.size(); ++i) {
-    fmt::print(stream, "  {}:\n", i);
-    fmt::print(stream, "    File info indexes:");
+    PRINT(stream, "  {}:\n", i);
+    PRINT_RAW(stream, "    File info indexes:");
     for (uint32_t file_info_index : mf->results[i].file_info_indexes) {
-      fmt::print(stream, " {}", file_info_index);
+      PRINT(stream, " {}", file_info_index);
     }
-    fmt::print(stream, "\n");
-    fmt::print(stream, "    Name: {}\n", mf->results[i].name.to_string());
+    PRINT_RAW(stream, "\n");
+    PRINT(stream, "    Name: {}\n", mf->results[i].name.to_string());
   }
 
   return true;
index 788d316..bf17397 100644 (file)
@@ -24,6 +24,7 @@
 #  include "MiniTrace.hpp"
 #  include "TemporaryFile.hpp"
 #  include "Util.hpp"
+#  include "fmtmacros.hpp"
 
 #  ifdef HAVE_SYS_TIME_H
 #    include <sys/time.h>
@@ -70,7 +71,7 @@ MiniTrace::MiniTrace(const ArgsInfo& args_info)
   m_tmp_trace_file = tmp_file.path;
 
   mtr_init(m_tmp_trace_file.c_str());
-  MTR_INSTANT_C("", "", "time", fmt::format("{:f}", time_seconds()).c_str());
+  MTR_INSTANT_C("", "", "time", FMT("{:f}", time_seconds()).c_str());
   MTR_META_PROCESS_NAME("ccache");
   MTR_START("program", "ccache", m_trace_id);
 }
index cb0811b..c7b219e 100644 (file)
@@ -18,6 +18,8 @@
 
 #include "ProgressBar.hpp"
 
+#include "fmtmacros.hpp"
+
 #include "third_party/fmt/core.h"
 
 #ifndef _WIN32
@@ -75,18 +77,19 @@ ProgressBar::update(double value)
   if (first_part_width + 10 > m_width) {
     // The progress bar would be less than 10 characters, so just print the
     // percentage.
-    fmt::print("\r{} {:5.1f}%", m_header, 100 * value);
+    PRINT(stdout, "\r{} {:5.1f}%", m_header, 100 * value);
   } else {
     size_t total_bar_width = m_width - first_part_width;
     size_t filled_bar_width = value * total_bar_width;
     size_t unfilled_bar_width = total_bar_width - filled_bar_width;
-    fmt::print("\r{} {:5.1f}% [{:=<{}}{: <{}}]",
-               m_header,
-               100 * value,
-               "",
-               filled_bar_width,
-               "",
-               unfilled_bar_width);
+    PRINT(stdout,
+          "\r{} {:5.1f}% [{:=<{}}{: <{}}]",
+          m_header,
+          100 * value,
+          "",
+          filled_bar_width,
+          "",
+          unfilled_bar_width);
   }
 
   fflush(stdout);
index 73a2680..ef8ac74 100644 (file)
@@ -30,6 +30,7 @@
 #include "Statistics.hpp"
 #include "Util.hpp"
 #include "exceptions.hpp"
+#include "fmtmacros.hpp"
 
 #include <algorithm>
 
@@ -89,7 +90,6 @@
 //
 // 1: Introduced in ccache 4.0.
 
-using Logging::log;
 using nonstd::nullopt;
 using nonstd::optional;
 using nonstd::string_view;
@@ -107,7 +107,7 @@ get_raw_file_path(string_view result_path, uint32_t entry_number)
 {
   const auto prefix = result_path.substr(
     0, result_path.length() - Result::k_file_suffix.length());
-  return fmt::format("{}{}W", prefix, entry_number);
+  return FMT("{}{}W", prefix, entry_number);
 }
 
 bool
@@ -184,7 +184,7 @@ gcno_file_in_mangled_form(const Context& ctx)
   const std::string abs_output_obj =
     Util::is_absolute_path(output_obj)
       ? output_obj
-      : fmt::format("{}/{}", ctx.apparent_cwd, output_obj);
+      : FMT("{}/{}", ctx.apparent_cwd, output_obj);
   std::string hashified_obj = abs_output_obj;
   std::replace(hashified_obj.begin(), hashified_obj.end(), '/', '#');
   return Util::change_extension(hashified_obj, ".gcno");
@@ -204,7 +204,7 @@ Result::Reader::Reader(const std::string& result_path)
 optional<std::string>
 Result::Reader::read(Consumer& consumer)
 {
-  log("Reading result {}", m_result_path);
+  LOG("Reading result {}", m_result_path);
 
   try {
     if (read_result(consumer)) {
@@ -350,12 +350,12 @@ Writer::do_finalize()
   for (const auto& pair : m_entries_to_write) {
     const auto file_type = pair.first;
     const auto& path = pair.second;
-    log("Storing result {}", path);
+    LOG("Storing result {}", path);
 
     const bool store_raw = should_store_raw_file(m_ctx.config, file_type);
     uint64_t file_size = Stat::stat(path, Stat::OnError::throw_error).size();
 
-    log("Storing {} file #{} {} ({} bytes) from {}",
+    LOG("Storing {} file #{} {} ({} bytes) from {}",
         store_raw ? "raw" : "embedded",
         entry_number,
         file_type_to_string(file_type),
index 7c740af..1e3bac4 100644 (file)
@@ -21,6 +21,7 @@
 #include "CacheEntryReader.hpp"
 #include "Context.hpp"
 #include "Logging.hpp"
+#include "fmtmacros.hpp"
 
 using nonstd::optional;
 
@@ -40,12 +41,12 @@ ResultDumper::on_entry_start(uint32_t entry_number,
                              uint64_t file_len,
                              optional<std::string> raw_file)
 {
-  fmt::print(m_stream,
-             "{} file #{}: {} ({} bytes)\n",
-             raw_file ? "Raw" : "Embedded",
-             entry_number,
-             Result::file_type_to_string(file_type),
-             file_len);
+  PRINT(m_stream,
+        "{} file #{}: {} ({} bytes)\n",
+        raw_file ? "Raw" : "Embedded",
+        entry_number,
+        Result::file_type_to_string(file_type),
+        file_len);
 }
 
 void
index 10b8189..e18ae51 100644 (file)
@@ -19,6 +19,7 @@
 #include "ResultExtractor.hpp"
 
 #include "Util.hpp"
+#include "fmtmacros.hpp"
 
 ResultExtractor::ResultExtractor(const std::string& directory)
   : m_directory(directory)
@@ -38,13 +39,13 @@ ResultExtractor::on_entry_start(uint32_t /*entry_number*/,
 {
   std::string suffix = Result::file_type_to_string(file_type);
   if (suffix == Result::k_unknown_file_type) {
-    suffix = fmt::format(".type_{}", file_type);
+    suffix = FMT(".type_{}", file_type);
   } else if (suffix[0] == '<') {
     suffix[0] = '.';
     suffix.resize(suffix.length() - 1);
   }
 
-  m_dest_path = fmt::format("{}/ccache-result{}", m_directory, suffix);
+  m_dest_path = FMT("{}/ccache-result{}", m_directory, suffix);
 
   if (!raw_file) {
     m_dest_fd = Fd(
index 263f494..77e044d 100644 (file)
@@ -19,9 +19,9 @@
 #include "ResultRetriever.hpp"
 
 #include "Context.hpp"
+#include "Depfile.hpp"
 #include "Logging.hpp"
 
-using Logging::log;
 using Result::FileType;
 
 ResultRetriever::ResultRetriever(Context& ctx, bool rewrite_dependency_target)
@@ -93,11 +93,11 @@ ResultRetriever::on_entry_start(uint32_t entry_number,
   }
 
   if (dest_path.empty()) {
-    log("Not copying");
+    LOG_RAW("Not copying");
   } else if (dest_path == "/dev/null") {
-    log("Not copying to /dev/null");
+    LOG_RAW("Not copying to /dev/null");
   } else {
-    log("Retrieving {} file #{} {} ({} bytes)",
+    LOG("Retrieving {} file #{} {} ({} bytes)",
         raw_file ? "raw" : "embedded",
         entry_number,
         Result::file_type_to_string(file_type),
@@ -110,7 +110,7 @@ ResultRetriever::on_entry_start(uint32_t entry_number,
       // if hard-linked, to make the object file newer than the source file).
       Util::update_mtime(*raw_file);
     } else {
-      log("Copying to {}", dest_path);
+      LOG("Copying to {}", dest_path);
       m_dest_fd = Fd(
         open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
       if (!m_dest_fd) {
@@ -164,9 +164,10 @@ ResultRetriever::write_dependency_file()
     if (m_rewrite_dependency_target) {
       size_t colon_pos = m_dest_data.find(':');
       if (colon_pos != std::string::npos) {
-        Util::write_fd(*m_dest_fd,
-                       m_ctx.args_info.output_obj.data(),
-                       m_ctx.args_info.output_obj.length());
+        const auto escaped_output_obj =
+          Depfile::escape_filename(m_ctx.args_info.output_obj);
+        Util::write_fd(
+          *m_dest_fd, escaped_output_obj.data(), escaped_output_obj.length());
         start_pos = colon_pos;
       }
     }
index 80805bb..ece4cc4 100644 (file)
@@ -20,8 +20,6 @@
 
 #include "Logging.hpp"
 
-using Logging::log;
-
 Stat::Stat(StatFunction stat_function,
            const std::string& path,
            Stat::OnError on_error)
@@ -35,7 +33,7 @@ Stat::Stat(StatFunction stat_function,
       throw Error("failed to stat {}: {}", path, strerror(errno));
     }
     if (on_error == OnError::log) {
-      log("Failed to stat {}: {}", path, strerror(errno));
+      LOG("Failed to stat {}: {}", path, strerror(errno));
     }
 
     // The file is missing, so just zero fill the stat structure. This will
index 0861de6..08ad892 100644 (file)
 #include "Logging.hpp"
 #include "Util.hpp"
 #include "exceptions.hpp"
-
-using Logging::log;
-using nonstd::nullopt;
-using nonstd::optional;
+#include "fmtmacros.hpp"
 
 const unsigned FLAG_NOZERO = 1; // don't zero with the -z option
 const unsigned FLAG_ALWAYS = 2; // always show, even if zero
 const unsigned FLAG_NEVER = 4;  // never show
 
-using Logging::log;
 using nonstd::nullopt;
 using nonstd::optional;
 
@@ -44,7 +40,7 @@ using FormatFunction = std::string (*)(uint64_t value);
 static std::string
 format_size(uint64_t size)
 {
-  return fmt::format("{:>11}", Util::format_human_readable_size(size));
+  return FMT("{:>11}", Util::format_human_readable_size(size));
 }
 
 static std::string
@@ -85,9 +81,9 @@ for_each_level_1_and_2_stats_file(
   const std::function<void(const std::string& path)> function)
 {
   for (size_t level_1 = 0; level_1 <= 0xF; ++level_1) {
-    function(fmt::format("{}/{:x}/stats", cache_dir, level_1));
+    function(FMT("{}/{:x}/stats", cache_dir, level_1));
     for (size_t level_2 = 0; level_2 <= 0xF; ++level_2) {
-      function(fmt::format("{}/{:x}/{:x}/stats", cache_dir, level_1, level_2));
+      function(FMT("{}/{:x}/{:x}/stats", cache_dir, level_1, level_2));
     }
   }
 }
@@ -225,7 +221,7 @@ update(const std::string& path,
 {
   Lockfile lock(path);
   if (!lock.acquired()) {
-    log("failed to acquire lock for {}", path);
+    LOG("Failed to acquire lock for {}", path);
     return nullopt;
   }
 
@@ -234,7 +230,7 @@ update(const std::string& path,
 
   AtomicFile file(path, AtomicFile::Mode::text);
   for (size_t i = 0; i < counters.size(); ++i) {
-    file.write(fmt::format("{}\n", counters.get_raw(i)));
+    file.write(FMT("{}\n", counters.get_raw(i)));
   }
   try {
     file.commit();
@@ -242,7 +238,7 @@ update(const std::string& path,
     // Make failure to write a stats file a soft error since it's not
     // important enough to fail whole the process and also because it is
     // called in the Context destructor.
-    log("Error: {}", e.what());
+    LOG("Error: {}", e.what());
   }
 
   return counters;
@@ -285,10 +281,9 @@ format_human_readable(const Config& config)
   std::tie(counters, last_updated) = collect_counters(config);
   std::string result;
 
-  result += fmt::format("{:36}{}\n", "cache directory", config.cache_dir());
-  result +=
-    fmt::format("{:36}{}\n", "primary config", config.primary_config_path());
-  result += fmt::format(
+  result += FMT("{:36}{}\n", "cache directory", config.cache_dir());
+  result += FMT("{:36}{}\n", "primary config", config.primary_config_path());
+  result += FMT(
     "{:36}{}\n", "secondary config (readonly)", config.secondary_config_path());
   if (last_updated > 0) {
     const auto tm = Util::localtime(last_updated);
@@ -296,7 +291,7 @@ format_human_readable(const Config& config)
     if (tm) {
       strftime(timestamp, sizeof(timestamp), "%c", &*tm);
     }
-    result += fmt::format("{:36}{}\n", "stats updated", timestamp);
+    result += FMT("{:36}{}\n", "stats updated", timestamp);
   }
 
   // ...and display them.
@@ -314,23 +309,23 @@ format_human_readable(const Config& config)
     const std::string value =
       k_statistics_fields[i].format
         ? k_statistics_fields[i].format(counters.get(statistic))
-        : fmt::format("{:8}", counters.get(statistic));
+        : FMT("{:8}", counters.get(statistic));
     if (!value.empty()) {
-      result += fmt::format("{:32}{}\n", k_statistics_fields[i].message, value);
+      result += FMT("{:32}{}\n", k_statistics_fields[i].message, value);
     }
 
     if (statistic == Statistic::cache_miss) {
       double percent = hit_rate(counters);
-      result += fmt::format("{:34}{:6.2f} %\n", "cache hit rate", percent);
+      result += FMT("{:34}{:6.2f} %\n", "cache hit rate", percent);
     }
   }
 
   if (config.max_files() != 0) {
-    result += fmt::format("{:32}{:8}\n", "max files", config.max_files());
+    result += FMT("{:32}{:8}\n", "max files", config.max_files());
   }
   if (config.max_size() != 0) {
-    result += fmt::format(
-      "{:32}{}\n", "max cache size", format_size(config.max_size()));
+    result +=
+      FMT("{:32}{}\n", "max cache size", format_size(config.max_size()));
   }
 
   return result;
@@ -344,13 +339,13 @@ format_machine_readable(const Config& config)
   std::tie(counters, last_updated) = collect_counters(config);
   std::string result;
 
-  result += fmt::format("stats_updated_timestamp\t{}\n", last_updated);
+  result += FMT("stats_updated_timestamp\t{}\n", last_updated);
 
   for (size_t i = 0; k_statistics_fields[i].message; i++) {
     if (!(k_statistics_fields[i].flags & FLAG_NEVER)) {
-      result += fmt::format("{}\t{}\n",
-                            k_statistics_fields[i].id,
-                            counters.get(k_statistics_fields[i].statistic));
+      result += FMT("{}\t{}\n",
+                    k_statistics_fields[i].id,
+                    counters.get(k_statistics_fields[i].statistic));
     }
   }
 
index 54519f7..bac6b7d 100644 (file)
@@ -24,6 +24,7 @@
 #include "FormatNonstdStringView.hpp"
 #include "Logging.hpp"
 #include "TemporaryFile.hpp"
+#include "fmtmacros.hpp"
 
 extern "C" {
 #include "third_party/base32hex.h"
@@ -72,11 +73,12 @@ extern "C" {
 #ifdef __APPLE__
 #  ifdef HAVE_SYS_CLONEFILE_H
 #    include <sys/clonefile.h>
-#    define FILE_CLONING_SUPPORTED 1
+#    ifdef CLONE_NOOWNERCOPY
+#      define FILE_CLONING_SUPPORTED 1
+#    endif
 #  endif
 #endif
 
-using Logging::log;
 using nonstd::nullopt;
 using nonstd::optional;
 using nonstd::string_view;
@@ -276,31 +278,31 @@ clone_hard_link_or_copy_file(const Context& ctx,
 {
   if (ctx.config.file_clone()) {
 #ifdef FILE_CLONING_SUPPORTED
-    log("Cloning {} to {}", source, dest);
+    LOG("Cloning {} to {}", source, dest);
     try {
       clone_file(source, dest, via_tmp_file);
       return;
     } catch (Error& e) {
-      log("Failed to clone: {}", e.what());
+      LOG("Failed to clone: {}", e.what());
     }
 #else
-    log("Not cloning {} to {} since it's unsupported");
+    LOG("Not cloning {} to {} since it's unsupported", source, dest);
 #endif
   }
   if (ctx.config.hard_link()) {
     unlink(dest.c_str());
-    log("Hard linking {} to {}", source, dest);
+    LOG("Hard linking {} to {}", source, dest);
     int ret = link(source.c_str(), dest.c_str());
     if (ret == 0) {
       if (chmod(dest.c_str(), 0444) != 0) {
-        log("Failed to chmod: {}", strerror(errno));
+        LOG("Failed to chmod: {}", strerror(errno));
       }
       return;
     }
-    log("Failed to hard link: {}", strerror(errno));
+    LOG("Failed to hard link: {}", strerror(errno));
   }
 
-  log("Copying {} to {}", source, dest);
+  LOG("Copying {} to {}", source, dest);
   copy_file(source, dest, via_tmp_file);
 }
 
@@ -502,7 +504,7 @@ for_each_level_1_subdir(const std::string& cache_dir,
   for (int i = 0; i <= 0xF; i++) {
     double progress = 1.0 * i / 16;
     progress_receiver(progress);
-    std::string subdir_path = fmt::format("{}/{:x}", cache_dir, i);
+    std::string subdir_path = FMT("{}/{:x}", cache_dir, i);
     visitor(subdir_path, [&](double inner_progress) {
       progress_receiver(progress + inner_progress / 16);
     });
@@ -552,11 +554,11 @@ std::string
 format_human_readable_size(uint64_t size)
 {
   if (size >= 1000 * 1000 * 1000) {
-    return fmt::format("{:.1f} GB", size / ((double)(1000 * 1000 * 1000)));
+    return FMT("{:.1f} GB", size / ((double)(1000 * 1000 * 1000)));
   } else if (size >= 1000 * 1000) {
-    return fmt::format("{:.1f} MB", size / ((double)(1000 * 1000)));
+    return FMT("{:.1f} MB", size / ((double)(1000 * 1000)));
   } else {
-    return fmt::format("{:.1f} kB", size / 1000.0);
+    return FMT("{:.1f} kB", size / 1000.0);
   }
 }
 
@@ -564,11 +566,11 @@ std::string
 format_parsable_size_with_suffix(uint64_t size)
 {
   if (size >= 1000 * 1000 * 1000) {
-    return fmt::format("{:.1f}G", size / ((double)(1000 * 1000 * 1000)));
+    return FMT("{:.1f}G", size / ((double)(1000 * 1000 * 1000)));
   } else if (size >= 1000 * 1000) {
-    return fmt::format("{:.1f}M", size / ((double)(1000 * 1000)));
+    return FMT("{:.1f}M", size / ((double)(1000 * 1000)));
   } else {
-    return fmt::format("{}", size);
+    return FMT("{}", size);
   }
 }
 
@@ -843,7 +845,7 @@ make_relative_path(const Context& ctx, string_view path)
   if (path.length() >= 3 && path[0] == '/') {
     if (isalpha(path[1]) && path[2] == '/') {
       // Transform /c/path... to c:/path...
-      winpath = fmt::format("{}:/{}", path[1], path.substr(3));
+      winpath = FMT("{}:/{}", path[1], path.substr(3));
       path = winpath;
     } else if (path[2] == ':') {
       // Transform /c:/path to c:/path
@@ -1141,7 +1143,7 @@ read_file(const std::string& path, size_t size_hint)
   }
 
   if (ret == -1) {
-    log("Failed reading {}", path);
+    LOG("Failed reading {}", path);
     throw Error(strerror(errno));
   }
 
@@ -1248,8 +1250,7 @@ same_program_name(nonstd::string_view program_name,
 #ifdef _WIN32
   std::string lowercase_program_name = Util::to_lowercase(program_name);
   return lowercase_program_name == canonical_program_name
-         || lowercase_program_name
-              == fmt::format("{}.exe", canonical_program_name);
+         || lowercase_program_name == FMT("{}.exe", canonical_program_name);
 #else
   return program_name == canonical_program_name;
 #endif
@@ -1457,9 +1458,9 @@ unlink_safe(const std::string& path, UnlinkLog unlink_log)
     }
   }
   if (success || unlink_log == UnlinkLog::log_failure) {
-    log("Unlink {} via {}", path, tmp_name);
+    LOG("Unlink {} via {}", path, tmp_name);
     if (!success) {
-      log("Unlink failed: {}", strerror(saved_errno));
+      LOG("Unlink failed: {}", strerror(saved_errno));
     }
   }
 
@@ -1476,9 +1477,9 @@ unlink_tmp(const std::string& path, UnlinkLog unlink_log)
     unlink(path.c_str()) == 0 || (errno == ENOENT || errno == ESTALE);
   saved_errno = errno;
   if (success || unlink_log == UnlinkLog::log_failure) {
-    log("Unlink {}", path);
+    LOG("Unlink {}", path);
     if (!success) {
-      log("Unlink failed: {}", strerror(saved_errno));
+      LOG("Unlink failed: {}", strerror(saved_errno));
     }
   }
 
index b3ab9e9..3fbab45 100644 (file)
@@ -455,7 +455,7 @@ void traverse(const std::string& path, const TraverseVisitor& visitor);
 
 // Remove `path` (non-directory), NFS safe. Logs according to `unlink_log`.
 //
-// Returns whether removal was successful. A non-existing `path` is considered
+// Returns whether removal was successful. A nonexistent `path` is considered
 // successful.
 bool unlink_safe(const std::string& path,
                  UnlinkLog unlink_log = UnlinkLog::log_failure);
@@ -463,7 +463,7 @@ bool unlink_safe(const std::string& path,
 // Remove `path` (non-directory), NFS hazardous. Use only for files that will
 // not exist on other systems. Logs according to `unlink_log`.
 //
-// Returns whether removal was successful. A non-existing `path` is considered
+// Returns whether removal was successful. A nonexistent `path` is considered
 // successful.
 bool unlink_tmp(const std::string& path,
                 UnlinkLog unlink_log = UnlinkLog::log_failure);
@@ -474,7 +474,7 @@ void unsetenv(const std::string& name);
 // Set mtime of `path` to the current timestamp.
 void update_mtime(const std::string& path);
 
-// Remove `path` (and its contents if it's a directory). A non-existing path is
+// Remove `path` (and its contents if it's a directory). A nonexistent path is
 // not considered an error.
 //
 // Throws Error on error.
index 8b2c518..a53cfa2 100644 (file)
 
 #include <algorithm>
 
-using Logging::log;
-
 ZstdCompressor::ZstdCompressor(FILE* stream, int8_t compression_level)
   : m_stream(stream), m_zstd_stream(ZSTD_createCStream())
 {
   if (compression_level == 0) {
     compression_level = default_compression_level;
-    log("Using default compression level {}", compression_level);
+    LOG("Using default compression level {}", compression_level);
   }
 
   // libzstd 1.3.4 and newer support negative levels. However, the query
   // function ZSTD_minCLevel did not appear until 1.3.6, so perform detection
   // based on version instead.
   if (ZSTD_versionNumber() < 10304 && compression_level < 1) {
-    log(
+    LOG(
       "Using compression level 1 (minimum level supported by libzstd) instead"
       " of {}",
       compression_level);
@@ -47,7 +45,7 @@ ZstdCompressor::ZstdCompressor(FILE* stream, int8_t compression_level)
 
   m_compression_level = std::min<int>(compression_level, ZSTD_maxCLevel());
   if (m_compression_level != compression_level) {
-    log("Using compression level {} (max libzstd level) instead of {}",
+    LOG("Using compression level {} (max libzstd level) instead of {}",
         m_compression_level,
         compression_level);
   }
index bda801d..1783f9f 100644 (file)
 #include "Logging.hpp"
 #include "assertions.hpp"
 #include "compopt.hpp"
+#include "fmtmacros.hpp"
 #include "language.hpp"
 
 #include <cassert>
 
-using Logging::log;
 using nonstd::nullopt;
 using nonstd::optional;
 using nonstd::string_view;
@@ -80,6 +80,11 @@ struct ArgumentProcessingState
   // compiler_only_args contains arguments that should only be passed to the
   // compiler, not the preprocessor.
   Args compiler_only_args;
+
+  // compiler_only_args_no_hash contains arguments that should only be passed to
+  // the compiler, not the preprocessor, and that also should not be part of the
+  // hash identifying the result.
+  Args compiler_only_args_no_hash;
 };
 
 bool
@@ -105,14 +110,14 @@ detect_pch(Context& ctx,
   std::string pch_file;
   if (option == "-include-pch" || option == "-include-pth") {
     if (Stat::stat(arg)) {
-      log("Detected use of precompiled header: {}", arg);
+      LOG("Detected use of precompiled header: {}", arg);
       pch_file = arg;
     }
   } else if (!is_cc1_option) {
     for (const auto& extension : {".gch", ".pch", ".pth"}) {
       std::string path = arg + extension;
       if (Stat::stat(path)) {
-        log("Detected use of precompiled header: {}", path);
+        LOG("Detected use of precompiled header: {}", path);
         pch_file = path;
       }
     }
@@ -120,7 +125,7 @@ detect_pch(Context& ctx,
 
   if (!pch_file.empty()) {
     if (!ctx.included_pch_file.empty()) {
-      log("Multiple precompiled headers used: {} and {}",
+      LOG("Multiple precompiled headers used: {} and {}",
           ctx.included_pch_file,
           pch_file);
       return false;
@@ -153,7 +158,7 @@ process_profiling_option(Context& ctx, const std::string& arg)
     new_profile_path = arg.substr(arg.find('=') + 1);
   } else if (arg == "-fprofile-generate" || arg == "-fprofile-instr-generate") {
     ctx.args_info.profile_generate = true;
-    if (ctx.guessed_compiler == GuessedCompiler::clang) {
+    if (ctx.config.compiler_type() == CompilerType::clang) {
       new_profile_path = ".";
     } else {
       // GCC uses $PWD/$(basename $obj).
@@ -177,13 +182,13 @@ process_profiling_option(Context& ctx, const std::string& arg)
     new_profile_use = true;
     new_profile_path = arg.substr(arg.find('=') + 1);
   } else {
-    log("Unknown profiling option: {}", arg);
+    LOG("Unknown profiling option: {}", arg);
     return false;
   }
 
   if (new_profile_use) {
     if (ctx.args_info.profile_use) {
-      log("Multiple profiling options not supported");
+      LOG_RAW("Multiple profiling options not supported");
       return false;
     }
     ctx.args_info.profile_use = true;
@@ -191,28 +196,18 @@ process_profiling_option(Context& ctx, const std::string& arg)
 
   if (!new_profile_path.empty()) {
     ctx.args_info.profile_path = new_profile_path;
-    log("Set profile directory to {}", ctx.args_info.profile_path);
+    LOG("Set profile directory to {}", ctx.args_info.profile_path);
   }
 
   if (ctx.args_info.profile_generate && ctx.args_info.profile_use) {
     // Too hard to figure out what the compiler will do.
-    log("Both generating and using profile info, giving up");
+    LOG_RAW("Both generating and using profile info, giving up");
     return false;
   }
 
   return true;
 }
 
-// The compiler is invoked with the original arguments in the depend mode.
-// Collect extra arguments that should be added.
-void
-add_depend_mode_extra_original_args(Context& ctx, const std::string& arg)
-{
-  if (ctx.config.depend_mode()) {
-    ctx.args_info.depend_extra_args.push_back(arg);
-  }
-}
-
 optional<Statistic>
 process_arg(Context& ctx,
             Args& args,
@@ -228,7 +223,7 @@ process_arg(Context& ctx,
   if (args[i] == "--ccache-skip") {
     i++;
     if (i == args.size()) {
-      log("--ccache-skip lacks an argument");
+      LOG_RAW("--ccache-skip lacks an argument");
       return Statistic::bad_compiler_arguments;
     }
     state.common_args.push_back(args[i]);
@@ -249,7 +244,7 @@ process_arg(Context& ctx,
     }
     auto file_args = Args::from_gcc_atfile(argpath);
     if (!file_args) {
-      log("Couldn't read arg file {}", argpath);
+      LOG("Couldn't read arg file {}", argpath);
       return Statistic::bad_compiler_arguments;
     }
 
@@ -259,10 +254,10 @@ process_arg(Context& ctx,
   }
 
   // Handle cuda "-optf" and "--options-file" argument.
-  if (ctx.guessed_compiler == GuessedCompiler::nvcc
+  if (config.compiler_type() == CompilerType::nvcc
       && (args[i] == "-optf" || args[i] == "--options-file")) {
     if (i == args.size() - 1) {
-      log("Expected argument after {}", args[i]);
+      LOG("Expected argument after {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
     ++i;
@@ -272,7 +267,7 @@ process_arg(Context& ctx,
     for (auto it = paths.rbegin(); it != paths.rend(); ++it) {
       auto file_args = Args::from_gcc_atfile(*it);
       if (!file_args) {
-        log("Couldn't read CUDA options file {}", *it);
+        LOG("Couldn't read CUDA options file {}", *it);
         return Statistic::bad_compiler_arguments;
       }
 
@@ -285,19 +280,19 @@ process_arg(Context& ctx,
   // These are always too hard.
   if (compopt_too_hard(args[i]) || Util::starts_with(args[i], "-fdump-")
       || Util::starts_with(args[i], "-MJ")) {
-    log("Compiler option {} is unsupported", args[i]);
+    LOG("Compiler option {} is unsupported", args[i]);
     return Statistic::unsupported_compiler_option;
   }
 
   // These are too hard in direct mode.
   if (config.direct_mode() && compopt_too_hard_for_direct_mode(args[i])) {
-    log("Unsupported compiler option for direct mode: {}", args[i]);
+    LOG("Unsupported compiler option for direct mode: {}", args[i]);
     config.set_direct_mode(false);
   }
 
   // -Xarch_* options are too hard.
   if (Util::starts_with(args[i], "-Xarch_")) {
-    log("Unsupported compiler option: {}", args[i]);
+    LOG("Unsupported compiler option: {}", args[i]);
     return Statistic::unsupported_compiler_option;
   }
 
@@ -332,10 +327,10 @@ process_arg(Context& ctx,
   if (compopt_affects_compiler_output(args[i])) {
     state.compiler_only_args.push_back(args[i]);
     if (compopt_takes_arg(args[i])
-        || (ctx.guessed_compiler == GuessedCompiler::nvcc
+        || (config.compiler_type() == CompilerType::nvcc
             && args[i] == "-Werror")) {
       if (i == args.size() - 1) {
-        log("Missing argument to {}", args[i]);
+        LOG("Missing argument to {}", args[i]);
         return Statistic::bad_compiler_arguments;
       }
       state.compiler_only_args.push_back(args[i + 1]);
@@ -358,11 +353,11 @@ process_arg(Context& ctx,
   // flag.
   if (args[i] == "-fmodules") {
     if (!config.depend_mode() || !config.direct_mode()) {
-      log("Compiler option {} is unsupported without direct depend mode",
+      LOG("Compiler option {} is unsupported without direct depend mode",
           args[i]);
       return Statistic::could_not_use_modules;
     } else if (!(config.sloppiness() & SLOPPY_MODULES)) {
-      log(
+      LOG_RAW(
         "You have to specify \"modules\" sloppiness when using"
         " -fmodules to get hits");
       return Statistic::could_not_use_modules;
@@ -377,7 +372,7 @@ process_arg(Context& ctx,
 
   // when using nvcc with separable compilation, -dc implies -c
   if ((args[i] == "-dc" || args[i] == "--device-c")
-      && ctx.guessed_compiler == GuessedCompiler::nvcc) {
+      && config.compiler_type() == CompilerType::nvcc) {
     state.found_dc_opt = true;
     return nullopt;
   }
@@ -403,7 +398,7 @@ process_arg(Context& ctx,
     // input file and strip all -x options from the arguments.
     if (args[i].length() == 2) {
       if (i == args.size() - 1) {
-        log("Missing argument to {}", args[i]);
+        LOG("Missing argument to {}", args[i]);
         return Statistic::bad_compiler_arguments;
       }
       if (args_info.input_file.empty()) {
@@ -423,7 +418,7 @@ process_arg(Context& ctx,
   // We need to work out where the output was meant to go.
   if (args[i] == "-o") {
     if (i == args.size() - 1) {
-      log("Missing argument to {}", args[i]);
+      LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
     args_info.output_obj = Util::make_relative_path(ctx, args[i + 1]);
@@ -433,7 +428,7 @@ process_arg(Context& ctx,
 
   // Alternate form of -o with no space. Nvcc does not support this.
   if (Util::starts_with(args[i], "-o")
-      && ctx.guessed_compiler != GuessedCompiler::nvcc) {
+      && config.compiler_type() != CompilerType::nvcc) {
     args_info.output_obj =
       Util::make_relative_path(ctx, string_view(args[i]).substr(2));
     return nullopt;
@@ -498,7 +493,7 @@ process_arg(Context& ctx,
     if (separate_argument) {
       // -MF arg
       if (i == args.size() - 1) {
-        log("Missing argument to {}", args[i]);
+        LOG("Missing argument to {}", args[i]);
         return Statistic::bad_compiler_arguments;
       }
       dep_file = args[i + 1];
@@ -524,7 +519,7 @@ process_arg(Context& ctx,
     if (args[i].size() == 3) {
       // -MQ arg or -MT arg
       if (i == args.size() - 1) {
-        log("Missing argument to {}", args[i]);
+        LOG("Missing argument to {}", args[i]);
         return Statistic::bad_compiler_arguments;
       }
       state.dep_args.push_back(args[i]);
@@ -535,7 +530,7 @@ process_arg(Context& ctx,
       auto arg_opt = string_view(args[i]).substr(0, 3);
       auto option = string_view(args[i]).substr(3);
       auto relpath = Util::make_relative_path(ctx, option);
-      state.dep_args.push_back(fmt::format("{}{}", arg_opt, relpath));
+      state.dep_args.push_back(FMT("{}{}", arg_opt, relpath));
     }
     return nullopt;
   }
@@ -558,6 +553,12 @@ process_arg(Context& ctx,
     return nullopt;
   }
 
+  if (args[i] == "-fsyntax-only") {
+    args_info.expect_output_obj = false;
+    state.compiler_only_args.push_back(args[i]);
+    return nullopt;
+  }
+
   if (args[i] == "--coverage"      // = -fprofile-arcs -ftest-coverage
       || args[i] == "-coverage") { // Undocumented but still works.
     args_info.profile_arcs = true;
@@ -593,7 +594,7 @@ process_arg(Context& ctx,
   // Alternate form of specifying sysroot without =
   if (args[i] == "--sysroot") {
     if (i == args.size() - 1) {
-      log("Missing argument to {}", args[i]);
+      LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
     state.common_args.push_back(args[i]);
@@ -606,7 +607,7 @@ process_arg(Context& ctx,
   // Alternate form of specifying target without =
   if (args[i] == "-target") {
     if (i == args.size() - 1) {
-      log("Missing argument to {}", args[i]);
+      LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
     state.common_args.push_back(args[i]);
@@ -621,7 +622,7 @@ process_arg(Context& ctx,
       // -P removes preprocessor information in such a way that the object file
       // from compiling the preprocessed file will not be equal to the object
       // file produced when compiling without ccache.
-      log("Too hard option -Wp,-P detected");
+      LOG_RAW("Too hard option -Wp,-P detected");
       return Statistic::unsupported_compiler_option;
     } else if (Util::starts_with(args[i], "-Wp,-MD,")
                && args[i].find(',', 8) == std::string::npos) {
@@ -656,7 +657,7 @@ process_arg(Context& ctx,
     } else if (config.direct_mode()) {
       // -Wp, can be used to pass too hard options to the preprocessor.
       // Hence, disable direct mode.
-      log("Unsupported compiler option for direct mode: {}", args[i]);
+      LOG("Unsupported compiler option for direct mode: {}", args[i]);
       config.set_direct_mode(false);
     }
 
@@ -678,7 +679,7 @@ process_arg(Context& ctx,
 
   if (args[i] == "--serialize-diagnostics") {
     if (i == args.size() - 1) {
-      log("Missing argument to {}", args[i]);
+      LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
     args_info.generating_diagnostics = true;
@@ -690,15 +691,18 @@ process_arg(Context& ctx,
   if (args[i] == "-fcolor-diagnostics" || args[i] == "-fdiagnostics-color"
       || args[i] == "-fdiagnostics-color=always") {
     state.color_diagnostics = ColorDiagnostics::always;
+    state.compiler_only_args_no_hash.push_back(args[i]);
     return nullopt;
   }
   if (args[i] == "-fno-color-diagnostics" || args[i] == "-fno-diagnostics-color"
       || args[i] == "-fdiagnostics-color=never") {
     state.color_diagnostics = ColorDiagnostics::never;
+    state.compiler_only_args_no_hash.push_back(args[i]);
     return nullopt;
   }
   if (args[i] == "-fdiagnostics-color=auto") {
     state.color_diagnostics = ColorDiagnostics::automatic;
+    state.compiler_only_args_no_hash.push_back(args[i]);
     return nullopt;
   }
 
@@ -733,7 +737,7 @@ process_arg(Context& ctx,
     // among multiple users.
     i++;
     if (i <= args.size() - 1) {
-      log("Skipping argument -index-store-path {}", args[i]);
+      LOG("Skipping argument -index-store-path {}", args[i]);
     }
     return nullopt;
   }
@@ -743,7 +747,7 @@ process_arg(Context& ctx,
   // output produced by the compiler will be normalized.
   if (compopt_takes_path(args[i])) {
     if (i == args.size() - 1) {
-      log("Missing argument to {}", args[i]);
+      LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
 
@@ -795,7 +799,7 @@ process_arg(Context& ctx,
   // Options that take an argument.
   if (compopt_takes_arg(args[i])) {
     if (i == args.size() - 1) {
-      log("Missing argument to {}", args[i]);
+      LOG("Missing argument to {}", args[i]);
       return Statistic::bad_compiler_arguments;
     }
 
@@ -830,7 +834,7 @@ process_arg(Context& ctx,
   if (args[i] != "/dev/null") {
     auto st = Stat::stat(args[i]);
     if (!st || !st.is_regular()) {
-      log("{} is not a regular file, not considering as input file", args[i]);
+      LOG("{} is not a regular file, not considering as input file", args[i]);
       state.common_args.push_back(args[i]);
       return nullopt;
     }
@@ -838,17 +842,17 @@ process_arg(Context& ctx,
 
   if (!args_info.input_file.empty()) {
     if (!language_for_file(args[i]).empty()) {
-      log("Multiple input files: {} and {}", args_info.input_file, args[i]);
+      LOG("Multiple input files: {} and {}", args_info.input_file, args[i]);
       return Statistic::multiple_source_files;
     } else if (!state.found_c_opt && !state.found_dc_opt) {
-      log("Called for link with {}", args[i]);
+      LOG("Called for link with {}", args[i]);
       if (args[i].find("conftest.") != std::string::npos) {
         return Statistic::autoconf_test;
       } else {
         return Statistic::called_for_link;
       }
     } else {
-      log("Unsupported source extension: {}", args[i]);
+      LOG("Unsupported source extension: {}", args[i]);
       return Statistic::unsupported_source_language;
     }
   }
@@ -903,8 +907,7 @@ handle_dependency_environment_variables(Context& ctx,
     string_view abspath_obj = dependencies[1];
     std::string relpath_obj = Util::make_relative_path(ctx, abspath_obj);
     // Ensure that the compiler gets a relative path.
-    std::string relpath_both =
-      fmt::format("{} {}", args_info.output_dep, relpath_obj);
+    std::string relpath_both = FMT("{} {}", args_info.output_dep, relpath_obj);
     if (using_sunpro_dependencies) {
       Util::setenv("SUNPRO_DEPENDENCIES", relpath_both);
     } else {
@@ -948,24 +951,24 @@ process_args(Context& ctx)
   }
 
   if (state.generating_debuginfo_level_3 && !config.run_second_cpp()) {
-    log("Generating debug info level 3; not compiling preprocessed code");
+    LOG_RAW("Generating debug info level 3; not compiling preprocessed code");
     config.set_run_second_cpp(true);
   }
 
   handle_dependency_environment_variables(ctx, state);
 
   if (args_info.input_file.empty()) {
-    log("No input file found");
+    LOG_RAW("No input file found");
     return Statistic::no_input_file;
   }
 
   if (state.found_pch || state.found_fpch_preprocess) {
     args_info.using_precompiled_header = true;
     if (!(config.sloppiness() & SLOPPY_TIME_MACROS)) {
-      log(
+      LOG_RAW(
         "You have to specify \"time_macros\" sloppiness when using"
         " precompiled headers to get direct hits");
-      log("Disabling direct mode");
+      LOG_RAW("Disabling direct mode");
       return Statistic::could_not_use_precompiled_header;
     }
   }
@@ -980,7 +983,7 @@ process_args(Context& ctx)
   state.file_language = language_for_file(args_info.input_file);
   if (!state.explicit_language.empty()) {
     if (!language_is_supported(state.explicit_language)) {
-      log("Unsupported language: {}", state.explicit_language);
+      LOG("Unsupported language: {}", state.explicit_language);
       return Statistic::unsupported_source_language;
     }
     args_info.actual_language = state.explicit_language;
@@ -994,7 +997,7 @@ process_args(Context& ctx)
 
   if (args_info.output_is_precompiled_header
       && !(config.sloppiness() & SLOPPY_PCH_DEFINES)) {
-    log(
+    LOG_RAW(
       "You have to specify \"pch_defines,time_macros\" sloppiness when"
       " creating precompiled headers");
     return Statistic::could_not_use_precompiled_header;
@@ -1004,7 +1007,7 @@ process_args(Context& ctx)
     if (args_info.output_is_precompiled_header) {
       state.common_args.push_back("-c");
     } else {
-      log("No -c option found");
+      LOG_RAW("No -c option found");
       // Having a separate statistic for autoconf tests is useful, as they are
       // the dominant form of "called for link" in many cases.
       return args_info.input_file.find("conftest.") != std::string::npos
@@ -1014,12 +1017,12 @@ process_args(Context& ctx)
   }
 
   if (args_info.actual_language.empty()) {
-    log("Unsupported source extension: {}", args_info.input_file);
+    LOG("Unsupported source extension: {}", args_info.input_file);
     return Statistic::unsupported_source_language;
   }
 
   if (!config.run_second_cpp() && args_info.actual_language == "cu") {
-    log("Using CUDA compiler; not compiling preprocessed code");
+    LOG_RAW("Using CUDA compiler; not compiling preprocessed code");
     config.set_run_second_cpp(true);
   }
 
@@ -1027,7 +1030,7 @@ process_args(Context& ctx)
 
   if (args_info.output_is_precompiled_header && !config.run_second_cpp()) {
     // It doesn't work to create the .gch from preprocessed source.
-    log("Creating precompiled header; not compiling preprocessed code");
+    LOG_RAW("Creating precompiled header; not compiling preprocessed code");
     config.set_run_second_cpp(true);
   }
 
@@ -1038,7 +1041,7 @@ process_args(Context& ctx)
 
   // Don't try to second guess the compilers heuristics for stdout handling.
   if (args_info.output_obj == "-") {
-    log("Output file is -");
+    LOG_RAW("Output file is -");
     return Statistic::output_to_stdout;
   }
 
@@ -1055,7 +1058,7 @@ process_args(Context& ctx)
   if (args_info.seen_split_dwarf) {
     size_t pos = args_info.output_obj.rfind('.');
     if (pos == std::string::npos || pos == args_info.output_obj.size() - 1) {
-      log("Badly formed object filename");
+      LOG_RAW("Badly formed object filename");
       return Statistic::bad_compiler_arguments;
     }
 
@@ -1066,7 +1069,7 @@ process_args(Context& ctx)
   if (args_info.output_obj != "/dev/null") {
     auto st = Stat::stat(args_info.output_obj);
     if (st && !st.is_regular()) {
-      log("Not a regular file: {}", args_info.output_obj);
+      LOG("Not a regular file: {}", args_info.output_obj);
       return Statistic::bad_output_file;
     }
   }
@@ -1074,7 +1077,7 @@ process_args(Context& ctx)
   auto output_dir = std::string(Util::dir_name(args_info.output_obj));
   auto st = Stat::stat(output_dir);
   if (!st || !st.is_directory()) {
-    log("Directory does not exist: {}", output_dir);
+    LOG("Directory does not exist: {}", output_dir);
     return Statistic::bad_output_file;
   }
 
@@ -1098,22 +1101,18 @@ process_args(Context& ctx)
     state.color_diagnostics != ColorDiagnostics::automatic
       ? state.color_diagnostics == ColorDiagnostics::never
       : !color_output_possible();
+
   // Since output is redirected, compilers will not color their output by
   // default, so force it explicitly.
-  if (ctx.guessed_compiler == GuessedCompiler::clang) {
+  nonstd::optional<std::string> diagnostics_color_arg;
+  if (config.compiler_type() == CompilerType::clang) {
+    // Don't pass -fcolor-diagnostics when compiling assembler to avoid an
+    // "argument unused during compilation" warning.
     if (args_info.actual_language != "assembler") {
-      if (!config.run_second_cpp()) {
-        state.cpp_args.push_back("-fcolor-diagnostics");
-      }
-      state.compiler_only_args.push_back("-fcolor-diagnostics");
-      add_depend_mode_extra_original_args(ctx, "-fcolor-diagnostics");
-    }
-  } else if (ctx.guessed_compiler == GuessedCompiler::gcc) {
-    if (!config.run_second_cpp()) {
-      state.cpp_args.push_back("-fdiagnostics-color");
+      diagnostics_color_arg = "-fcolor-diagnostics";
     }
-    state.compiler_only_args.push_back("-fdiagnostics-color");
-    add_depend_mode_extra_original_args(ctx, "-fdiagnostics-color");
+  } else if (config.compiler_type() == CompilerType::gcc) {
+    diagnostics_color_arg = "-fdiagnostics-color";
   } else {
     // Other compilers shouldn't output color, so no need to strip it.
     args_info.strip_diagnostics_colors = false;
@@ -1152,6 +1151,7 @@ process_args(Context& ctx)
   }
 
   Args compiler_args = state.common_args;
+  compiler_args.push_back(state.compiler_only_args_no_hash);
   compiler_args.push_back(state.compiler_only_args);
 
   if (config.run_second_cpp()) {
@@ -1212,5 +1212,18 @@ process_args(Context& ctx)
     extra_args_to_hash.push_back(state.dep_args);
   }
 
+  if (diagnostics_color_arg) {
+    compiler_args.push_back(*diagnostics_color_arg);
+    if (!config.run_second_cpp()) {
+      // If we're compiling preprocessed code we're keeping any warnings from
+      // the preprocessor, so we need to make sure that they are in color.
+      preprocessor_args.push_back(*diagnostics_color_arg);
+    }
+    if (ctx.config.depend_mode()) {
+      // The compiler is invoked with the original arguments in the depend mode.
+      ctx.args_info.depend_extra_args.push_back(*diagnostics_color_arg);
+    }
+  }
+
   return {preprocessor_args, extra_args_to_hash, compiler_args};
 }
index 3c5a563..2229d4f 100644 (file)
@@ -19,6 +19,7 @@
 #include "assertions.hpp"
 
 #include "Util.hpp"
+#include "fmtmacros.hpp"
 
 #include "third_party/fmt/core.h"
 
@@ -28,11 +29,11 @@ handle_failed_assertion(const char* file,
                         const char* function,
                         const char* condition)
 {
-  fmt::print(stderr,
-             "ccache: {}:{}: {}: failed assertion: {}\n",
-             Util::base_name(file),
-             line,
-             function,
-             condition);
+  PRINT(stderr,
+        "ccache: {}:{}: {}: failed assertion: {}\n",
+        Util::base_name(file),
+        line,
+        function,
+        condition);
   abort();
 }
index 676f9c0..b7c3013 100644 (file)
@@ -24,6 +24,7 @@
 #include "Checksum.hpp"
 #include "Compression.hpp"
 #include "Context.hpp"
+#include "Depfile.hpp"
 #include "Fd.hpp"
 #include "File.hpp"
 #include "Finalizer.hpp"
@@ -49,6 +50,7 @@
 #include "compress.hpp"
 #include "exceptions.hpp"
 #include "execute.hpp"
+#include "fmtmacros.hpp"
 #include "hashutil.hpp"
 #include "language.hpp"
 
 #endif
 const char CCACHE_NAME[] = MYNAME;
 
-using Logging::log;
 using nonstd::nullopt;
 using nonstd::optional;
 using nonstd::string_view;
 
-const char VERSION_TEXT[] =
+constexpr const char VERSION_TEXT[] =
   R"({} version {}
 
 Copyright (C) 2002-2007 Andrew Tridgell
@@ -96,7 +97,7 @@ Foundation; either version 3 of the License, or (at your option) any later
 version.
 )";
 
-const char USAGE_TEXT[] =
+constexpr const char USAGE_TEXT[] =
   R"(Usage:
     {} [options]
     {} compiler [compiler options]
@@ -107,6 +108,8 @@ Common options:
                                (normally not needed as this is done
                                automatically)
     -C, --clear                clear the cache completely (except configuration)
+        --config-path PATH     operate on configuration file PATH instead of the
+                               default
     -d, --directory PATH       operate on cache directory PATH instead of the
                                default
         --evict-older-than AGE remove files older than AGE (unsigned integer
@@ -194,7 +197,7 @@ add_prefix(const Context& ctx, Args& args, const std::string& prefix_command)
     prefix.push_back(path);
   }
 
-  log("Using command-line prefix {}", prefix_command);
+  LOG("Using command-line prefix {}", prefix_command);
   for (size_t i = prefix.size(); i != 0; i--) {
     args.push_front(prefix[i - 1]);
   }
@@ -240,32 +243,51 @@ init_hash_debug(Context& ctx,
     return;
   }
 
-  std::string path = fmt::format("{}.ccache-input-{}", obj_path, type);
+  std::string path = FMT("{}.ccache-input-{}", obj_path, type);
   File debug_binary_file(path, "wb");
   if (debug_binary_file) {
     hash.enable_debug(section_name, debug_binary_file.get(), debug_text_file);
     ctx.hash_debug_files.push_back(std::move(debug_binary_file));
   } else {
-    log("Failed to open {}: {}", path, strerror(errno));
+    LOG("Failed to open {}: {}", path, strerror(errno));
   }
 }
 
-static GuessedCompiler
+CompilerType
 guess_compiler(string_view path)
 {
-  string_view name = Util::base_name(path);
-  GuessedCompiler result = GuessedCompiler::unknown;
-  if (name.find("clang") != std::string::npos) {
-    result = GuessedCompiler::clang;
-  } else if (name.find("gcc") != std::string::npos
-             || name.find("g++") != std::string::npos) {
-    result = GuessedCompiler::gcc;
-  } else if (name.find("nvcc") != std::string::npos) {
-    result = GuessedCompiler::nvcc;
+  std::string compiler_path(path);
+
+#ifndef _WIN32
+  // Follow symlinks to the real compiler to learn its name. We're not using
+  // Util::real_path in order to save some unnecessary stat calls.
+  while (true) {
+    std::string symlink_value = Util::read_link(compiler_path);
+    if (symlink_value.empty()) {
+      break;
+    }
+    if (Util::is_absolute_path(symlink_value)) {
+      compiler_path = symlink_value;
+    } else {
+      compiler_path =
+        FMT("{}/{}", Util::dir_name(compiler_path), symlink_value);
+    }
+  }
+#endif
+
+  const string_view name = Util::base_name(compiler_path);
+  if (name.find("clang") != nonstd::string_view::npos) {
+    return CompilerType::clang;
+  } else if (name.find("gcc") != nonstd::string_view::npos
+             || name.find("g++") != nonstd::string_view::npos) {
+    return CompilerType::gcc;
+  } else if (name.find("nvcc") != nonstd::string_view::npos) {
+    return CompilerType::nvcc;
   } else if (name == "pump" || name == "distcc-pump") {
-    result = GuessedCompiler::pump;
+    return CompilerType::pump;
+  } else {
+    return CompilerType::other;
   }
-  return result;
 }
 
 static bool
@@ -323,7 +345,7 @@ do_remember_include_file(Context& ctx,
   }
   if (!st.is_regular()) {
     // Device, pipe, socket or other strange creature.
-    log("Non-regular include file {}", path);
+    LOG("Non-regular include file {}", path);
     return false;
   }
 
@@ -338,14 +360,14 @@ do_remember_include_file(Context& ctx,
   // under "Performance" in doc/MANUAL.adoc.
   if (!(ctx.config.sloppiness() & SLOPPY_INCLUDE_FILE_MTIME)
       && st.mtime() >= ctx.time_of_compilation) {
-    log("Include file {} too new", path);
+    LOG("Include file {} too new", path);
     return false;
   }
 
   // The same >= logic as above applies to the change time of the file.
   if (!(ctx.config.sloppiness() & SLOPPY_INCLUDE_FILE_CTIME)
       && st.ctime() >= ctx.time_of_compilation) {
-    log("Include file {} ctime too new", path);
+    LOG("Include file {} ctime too new", path);
     return false;
   }
 
@@ -355,17 +377,17 @@ do_remember_include_file(Context& ctx,
   is_pch = Util::is_precompiled_header(path);
   if (is_pch) {
     if (ctx.included_pch_file.empty()) {
-      log("Detected use of precompiled header: {}", path);
+      LOG("Detected use of precompiled header: {}", path);
     }
     bool using_pch_sum = false;
     if (ctx.config.pch_external_checksum()) {
       // hash pch.sum instead of pch when it exists
       // to prevent hashing a very large .pch file every time
-      std::string pch_sum_path = fmt::format("{}.sum", path);
+      std::string pch_sum_path = FMT("{}.sum", path);
       if (Stat::stat(pch_sum_path, Stat::OnError::log)) {
         path = std::move(pch_sum_path);
         using_pch_sum = true;
-        log("Using pch.sum file {}", path);
+        LOG("Using pch.sum file {}", path);
       }
     }
 
@@ -408,7 +430,7 @@ remember_include_file(Context& ctx,
 {
   if (!do_remember_include_file(ctx, path, cpp_hash, system, depend_mode_hash)
       && ctx.config.direct_mode()) {
-    log("Disabling direct mode");
+    LOG_RAW("Disabling direct mode");
     ctx.config.set_direct_mode(false);
   }
 }
@@ -417,7 +439,7 @@ static void
 print_included_files(const Context& ctx, FILE* fp)
 {
   for (const auto& item : ctx.included_files) {
-    fmt::print(fp, "{}\n", item.first);
+    PRINT(fp, "{}\n", item.first);
   }
 }
 
@@ -517,7 +539,7 @@ process_preprocessed_file(Context& ctx,
       }
       q++;
       if (q >= end) {
-        log("Failed to parse included file path");
+        LOG_RAW("Failed to parse included file path");
         return false;
       }
       // q points to the beginning of an include file path
@@ -568,7 +590,7 @@ process_preprocessed_file(Context& ctx,
       // part of inline assembly, refers to an external file. If the file
       // changes, the hash should change as well, but finding out what file to
       // hash is too hard for ccache, so just bail out.
-      log(
+      LOG_RAW(
         "Found unsupported .inc"
         "bin directive in source code");
       throw Failure(Statistic::unsupported_code_directive);
@@ -608,83 +630,6 @@ process_preprocessed_file(Context& ctx,
   return true;
 }
 
-nonstd::optional<std::string>
-rewrite_dep_file_paths(const Context& ctx, const std::string& file_content)
-{
-  ASSERT(!ctx.config.base_dir().empty());
-  ASSERT(ctx.has_absolute_include_headers);
-
-  // Fast path for the common case:
-  if (file_content.find(ctx.config.base_dir()) == std::string::npos) {
-    return nonstd::nullopt;
-  }
-
-  std::string adjusted_file_content;
-  adjusted_file_content.reserve(file_content.size());
-
-  bool content_rewritten = false;
-  for (const auto& line : Util::split_into_views(file_content, "\n")) {
-    const auto tokens = Util::split_into_views(line, " \t");
-    for (size_t i = 0; i < tokens.size(); ++i) {
-      DEBUG_ASSERT(line.length() > 0); // line.empty() -> no tokens
-      if (i > 0 || line[0] == ' ' || line[0] == '\t') {
-        adjusted_file_content.push_back(' ');
-      }
-
-      const auto& token = tokens[i];
-      bool token_rewritten = false;
-      if (Util::is_absolute_path(token)) {
-        const auto new_path = Util::make_relative_path(ctx, token);
-        if (new_path != token) {
-          adjusted_file_content.append(new_path);
-          token_rewritten = true;
-        }
-      }
-      if (token_rewritten) {
-        content_rewritten = true;
-      } else {
-        adjusted_file_content.append(token.begin(), token.end());
-      }
-    }
-    adjusted_file_content.push_back('\n');
-  }
-
-  if (content_rewritten) {
-    return adjusted_file_content;
-  } else {
-    return nonstd::nullopt;
-  }
-}
-
-// Replace absolute paths with relative paths in the provided dependency file.
-static void
-use_relative_paths_in_depfile(const Context& ctx)
-{
-  if (ctx.config.base_dir().empty()) {
-    log("Base dir not set, skip using relative paths");
-    return; // nothing to do
-  }
-  if (!ctx.has_absolute_include_headers) {
-    log("No absolute path for included files found, skip using relative paths");
-    return; // nothing to do
-  }
-
-  const std::string& output_dep = ctx.args_info.output_dep;
-  std::string file_content;
-  try {
-    file_content = Util::read_file(output_dep);
-  } catch (const Error& e) {
-    log("Cannot open dependency file {}: {}", output_dep, e.what());
-    return;
-  }
-  const auto new_content = rewrite_dep_file_paths(ctx, file_content);
-  if (new_content) {
-    Util::write_file(output_dep, *new_content);
-  } else {
-    log("No paths in dependency file {} made relative", output_dep);
-  }
-}
-
 // Extract the used includes from the dependency file. Note that we cannot
 // distinguish system headers from other includes here.
 static optional<Digest>
@@ -694,13 +639,13 @@ result_name_from_depfile(Context& ctx, Hash& hash)
   try {
     file_content = Util::read_file(ctx.args_info.output_dep);
   } catch (const Error& e) {
-    log(
+    LOG(
       "Cannot open dependency file {}: {}", ctx.args_info.output_dep, e.what());
     return nullopt;
   }
 
-  for (string_view token : Util::split_into_views(file_content, " \t\r\n")) {
-    if (token == "\\" || token.ends_with(":")) {
+  for (string_view token : Depfile::tokenize(file_content)) {
+    if (token.ends_with(":")) {
       continue;
     }
     if (!ctx.has_absolute_include_headers) {
@@ -725,7 +670,6 @@ result_name_from_depfile(Context& ctx, Hash& hash)
 
   return hash.digest();
 }
-
 // Execute the compiler/preprocessor, with logic to retry without requesting
 // colored diagnostics messages if that fails.
 static int
@@ -736,33 +680,39 @@ do_execute(Context& ctx,
 {
   UmaskScope umask_scope(ctx.original_umask);
 
-  if (ctx.diagnostics_color_failed
-      && ctx.guessed_compiler == GuessedCompiler::gcc) {
-    args.erase_with_prefix("-fdiagnostics-color");
+  if (ctx.diagnostics_color_failed) {
+    DEBUG_ASSERT(ctx.config.compiler_type() == CompilerType::gcc);
+    args.erase_last("-fdiagnostics-color");
   }
   int status = execute(args.to_argv().data(),
                        std::move(tmp_stdout.fd),
                        std::move(tmp_stderr.fd),
                        &ctx.compiler_pid);
   if (status != 0 && !ctx.diagnostics_color_failed
-      && ctx.guessed_compiler == GuessedCompiler::gcc) {
+      && ctx.config.compiler_type() == CompilerType::gcc) {
     auto errors = Util::read_file(tmp_stderr.path);
-    if (errors.find("unrecognized command line option") != std::string::npos
-        && errors.find("-fdiagnostics-color") != std::string::npos) {
-      // Old versions of GCC do not support colored diagnostics.
-      log("-fdiagnostics-color is unsupported; trying again without it");
+    if (errors.find("fdiagnostics-color") != std::string::npos) {
+      // GCC versions older than 4.9 don't understand -fdiagnostics-color, and
+      // non-GCC compilers misclassified as CompilerType::gcc might not do it
+      // either. We assume that if the error message contains
+      // "fdiagnostics-color" then the compilation failed due to
+      // -fdiagnostics-color being unsupported and we then retry without the
+      // flag. (Note that there intentionally is no leading dash in
+      // "fdiagnostics-color" since some compilers don't include the dash in the
+      // error message.)
+      LOG_RAW("-fdiagnostics-color is unsupported; trying again without it");
 
       tmp_stdout.fd = Fd(open(
         tmp_stdout.path.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600));
       if (!tmp_stdout.fd) {
-        log("Failed to truncate {}: {}", tmp_stdout.path, strerror(errno));
+        LOG("Failed to truncate {}: {}", tmp_stdout.path, strerror(errno));
         throw Failure(Statistic::internal_error);
       }
 
       tmp_stderr.fd = Fd(open(
         tmp_stderr.path.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600));
       if (!tmp_stderr.fd) {
-        log("Failed to truncate {}: {}", tmp_stderr.path, strerror(errno));
+        LOG("Failed to truncate {}: {}", tmp_stderr.path, strerror(errno));
         throw Failure(Statistic::internal_error);
       }
 
@@ -786,7 +736,7 @@ look_up_cache_file(const std::string& cache_dir,
                    const Digest& name,
                    nonstd::string_view suffix)
 {
-  const auto name_string = fmt::format("{}{}", name.to_string(), suffix);
+  const auto name_string = FMT("{}{}", name.to_string(), suffix);
 
   for (uint8_t level = k_min_cache_levels; level <= k_max_cache_levels;
        ++level) {
@@ -824,14 +774,14 @@ update_manifest_file(Context& ctx)
     (ctx.config.sloppiness() & SLOPPY_FILE_STAT_MATCHES)
     || ctx.args_info.output_is_precompiled_header;
 
-  log("Adding result name to {}", *ctx.manifest_path());
+  LOG("Adding result name to {}", *ctx.manifest_path());
   if (!Manifest::put(ctx.config,
                      *ctx.manifest_path(),
                      *ctx.result_name(),
                      ctx.included_files,
                      ctx.time_of_compilation,
                      save_timestamp)) {
-    log("Failed to add result name to {}", *ctx.manifest_path());
+    LOG("Failed to add result name to {}", *ctx.manifest_path());
   } else {
     const auto new_stat = Stat::stat(*ctx.manifest_path(), Stat::OnError::log);
     ctx.manifest_counter_updates.increment(
@@ -852,9 +802,9 @@ create_cachedir_tag(const Context& ctx)
     "# For information about cache directory tags, see:\n"
     "#\thttp://www.brynosaurus.com/cachedir/\n";
 
-  const std::string path = fmt::format("{}/{}/CACHEDIR.TAG",
-                                       ctx.config.cache_dir(),
-                                       ctx.result_name()->to_string()[0]);
+  const std::string path = FMT("{}/{}/CACHEDIR.TAG",
+                               ctx.config.cache_dir(),
+                               ctx.result_name()->to_string()[0]);
   const auto stat = Stat::stat(path);
   if (stat) {
     return;
@@ -862,7 +812,7 @@ create_cachedir_tag(const Context& ctx)
   try {
     Util::write_file(path, cachedir_tag);
   } catch (const Error& e) {
-    log("Failed to create {}: {}", path, e.what());
+    LOG("Failed to create {}: {}", path, e.what());
   }
 }
 
@@ -884,19 +834,19 @@ find_coverage_file(const Context& ctx)
   std::string unmangled_form = Result::gcno_file_in_unmangled_form(ctx);
   std::string found_file;
   if (Stat::stat(mangled_form)) {
-    log("Found coverage file {}", mangled_form);
+    LOG("Found coverage file {}", mangled_form);
     found_file = mangled_form;
   }
   if (Stat::stat(unmangled_form)) {
-    log("Found coverage file {}", unmangled_form);
+    LOG("Found coverage file {}", unmangled_form);
     if (!found_file.empty()) {
-      log("Found two coverage files, cannot continue");
+      LOG_RAW("Found two coverage files, cannot continue");
       return {};
     }
     found_file = unmangled_form;
   }
   if (found_file.empty()) {
-    log("No coverage file found (tried {} and {}), cannot continue",
+    LOG("No coverage file found (tried {} and {}), cannot continue",
         unmangled_form,
         mangled_form);
     return {};
@@ -943,24 +893,22 @@ to_cache(Context& ctx,
     // Remove any pre-existing .dwo file since we want to check if the compiler
     // produced one, intentionally not using x_unlink or tmp_unlink since we're
     // not interested in logging successful deletions or failures due to
-    // non-existent .dwo files.
+    // nonexistent .dwo files.
     if (unlink(ctx.args_info.output_dwo.c_str()) != 0 && errno != ENOENT
         && errno != ESTALE) {
-      log("Failed to unlink {}: {}", ctx.args_info.output_dwo, strerror(errno));
+      LOG("Failed to unlink {}: {}", ctx.args_info.output_dwo, strerror(errno));
       throw Failure(Statistic::bad_output_file);
     }
   }
 
-  log("Running real compiler");
+  LOG_RAW("Running real compiler");
   MTR_BEGIN("execute", "compiler");
 
-  TemporaryFile tmp_stdout(
-    fmt::format("{}/tmp.stdout", ctx.config.temporary_dir()));
+  TemporaryFile tmp_stdout(FMT("{}/tmp.stdout", ctx.config.temporary_dir()));
   ctx.register_pending_tmp_file(tmp_stdout.path);
   std::string tmp_stdout_path = tmp_stdout.path;
 
-  TemporaryFile tmp_stderr(
-    fmt::format("{}/tmp.stderr", ctx.config.temporary_dir()));
+  TemporaryFile tmp_stderr(FMT("{}/tmp.stderr", ctx.config.temporary_dir()));
   ctx.register_pending_tmp_file(tmp_stderr.path);
   std::string tmp_stderr_path = tmp_stderr.path;
 
@@ -991,8 +939,8 @@ to_cache(Context& ctx,
 
   // distcc-pump outputs lines like this:
   // __________Using # distcc servers in pump mode
-  if (st.size() != 0 && ctx.guessed_compiler != GuessedCompiler::pump) {
-    log("Compiler produced stdout");
+  if (st.size() != 0 && ctx.config.compiler_type() != CompilerType::pump) {
+    LOG_RAW("Compiler produced stdout");
     throw Failure(Statistic::compiler_produced_stdout);
   }
 
@@ -1005,7 +953,7 @@ to_cache(Context& ctx,
   }
 
   if (status != 0) {
-    log("Compiler gave exit status {}", status);
+    LOG("Compiler gave exit status {}", status);
 
     // We can output stderr immediately instead of rerunning the compiler.
     Util::send_to_stderr(ctx, Util::read_file(tmp_stderr_path));
@@ -1026,16 +974,19 @@ to_cache(Context& ctx,
                           && ctx.args_info.output_dep != "/dev/null";
 
   if (produce_dep_file) {
-    use_relative_paths_in_depfile(ctx);
+    Depfile::make_paths_relative_in_output_dep(ctx);
   }
 
   const auto obj_stat = Stat::stat(ctx.args_info.output_obj);
   if (!obj_stat) {
-    log("Compiler didn't produce an object file");
-    throw Failure(Statistic::compiler_produced_no_output);
-  }
-  if (obj_stat.size() == 0) {
-    log("Compiler produced an empty object file");
+    if (ctx.args_info.expect_output_obj) {
+      LOG_RAW("Compiler didn't produce an object file (unexpected)");
+      throw Failure(Statistic::compiler_produced_no_output);
+    } else {
+      LOG_RAW("Compiler didn't produce an object file (expected)");
+    }
+  } else if (obj_stat.size() == 0) {
+    LOG_RAW("Compiler produced an empty object file");
     throw Failure(Statistic::compiler_produced_empty_output);
   }
 
@@ -1052,7 +1003,9 @@ to_cache(Context& ctx,
   if (stderr_stat.size() > 0) {
     result_writer.write(Result::FileType::stderr_output, tmp_stderr_path);
   }
-  result_writer.write(Result::FileType::object, ctx.args_info.output_obj);
+  if (obj_stat) {
+    result_writer.write(Result::FileType::object, ctx.args_info.output_obj);
+  }
   if (ctx.args_info.generating_dependencies) {
     result_writer.write(Result::FileType::dependency, ctx.args_info.output_dep);
   }
@@ -1081,9 +1034,9 @@ to_cache(Context& ctx,
 
   auto error = result_writer.finalize();
   if (error) {
-    log("Error: {}", *error);
+    LOG("Error: {}", *error);
   } else {
-    log("Stored in cache: {}", result_file.path);
+    LOG("Stored in cache: {}", result_file.path);
   }
 
   auto new_result_stat = Stat::stat(result_file.path, Stat::OnError::log);
@@ -1126,12 +1079,12 @@ get_result_name_from_cpp(Context& ctx, Args& args, Hash& hash)
     // Run cpp on the input file to obtain the .i.
 
     TemporaryFile tmp_stdout(
-      fmt::format("{}/tmp.cpp_stdout", ctx.config.temporary_dir()));
+      FMT("{}/tmp.cpp_stdout", ctx.config.temporary_dir()));
     stdout_path = tmp_stdout.path;
     ctx.register_pending_tmp_file(stdout_path);
 
     TemporaryFile tmp_stderr(
-      fmt::format("{}/tmp.cpp_stderr", ctx.config.temporary_dir()));
+      FMT("{}/tmp.cpp_stderr", ctx.config.temporary_dir()));
     stderr_path = tmp_stderr.path;
     ctx.register_pending_tmp_file(stderr_path);
 
@@ -1148,7 +1101,7 @@ get_result_name_from_cpp(Context& ctx, Args& args, Hash& hash)
     }
     args.push_back(ctx.args_info.input_file);
     add_prefix(ctx, args, ctx.config.prefix_command_cpp());
-    log("Running preprocessor");
+    LOG_RAW("Running preprocessor");
     MTR_BEGIN("execute", "preprocessor");
     status =
       do_execute(ctx, args, std::move(tmp_stdout), std::move(tmp_stderr));
@@ -1157,12 +1110,12 @@ get_result_name_from_cpp(Context& ctx, Args& args, Hash& hash)
   }
 
   if (status != 0) {
-    log("Preprocessor gave exit status {}", status);
+    LOG("Preprocessor gave exit status {}", status);
     throw Failure(Statistic::preprocessor_error);
   }
 
   hash.hash_delimiter("cpp");
-  bool is_pump = ctx.guessed_compiler == GuessedCompiler::pump;
+  bool is_pump = ctx.config.compiler_type() == CompilerType::pump;
   if (!process_preprocessed_file(ctx, hash, stdout_path, is_pump)) {
     throw Failure(Statistic::internal_error);
   }
@@ -1170,7 +1123,7 @@ get_result_name_from_cpp(Context& ctx, Args& args, Hash& hash)
   hash.hash_delimiter("cppstderr");
   if (!ctx.args_info.direct_i_file && !hash.hash_file(stderr_path)) {
     // Somebody removed the temporary file?
-    log("Failed to open {}: {}", stderr_path, strerror(errno));
+    LOG("Failed to open {}: {}", stderr_path, strerror(errno));
     throw Failure(Statistic::internal_error);
   }
 
@@ -1179,8 +1132,7 @@ get_result_name_from_cpp(Context& ctx, Args& args, Hash& hash)
   } else {
     // i_tmpfile needs the proper cpp_extension for the compiler to do its
     // thing correctly
-    ctx.i_tmpfile =
-      fmt::format("{}.{}", stdout_path, ctx.config.cpp_extension());
+    ctx.i_tmpfile = FMT("{}.{}", stdout_path, ctx.config.cpp_extension());
     Util::rename(stdout_path, ctx.i_tmpfile);
     ctx.register_pending_tmp_file(ctx.i_tmpfile);
   }
@@ -1220,7 +1172,7 @@ hash_compiler(const Context& ctx,
   } else { // command string
     if (!hash_multicommand_output(
           hash, ctx.config.compiler_check(), ctx.orig_args[0])) {
-      log("Failure running compiler check command: {}",
+      LOG("Failure running compiler check command: {}",
           ctx.config.compiler_check());
       throw Failure(Statistic::compiler_check_failed);
     }
@@ -1260,7 +1212,7 @@ hash_nvcc_host_compiler(const Context& ctx,
 #endif
     for (const char* compiler : compilers) {
       if (!ccbin.empty()) {
-        std::string path = fmt::format("{}/{}", ccbin, compiler);
+        std::string path = FMT("{}/{}", ccbin, compiler);
         auto st = Stat::stat(path);
         if (st) {
           hash_compiler(ctx, hash, st, path, false);
@@ -1355,7 +1307,7 @@ hash_common_info(const Context& ctx,
       if (sep_pos != std::string::npos) {
         std::string old_path = map.substr(0, sep_pos);
         std::string new_path = map.substr(sep_pos + 1);
-        log("Relocating debuginfo from {} to {} (CWD: {})",
+        LOG("Relocating debuginfo from {} to {} (CWD: {})",
             old_path,
             new_path,
             ctx.apparent_cwd);
@@ -1364,7 +1316,7 @@ hash_common_info(const Context& ctx,
         }
       }
     }
-    log("Hashing CWD {}", dir_to_hash);
+    LOG("Hashing CWD {}", dir_to_hash);
     hash.hash_delimiter("cwd");
     hash.hash(dir_to_hash);
   }
@@ -1394,15 +1346,15 @@ hash_common_info(const Context& ctx,
     }
     string_view stem =
       Util::remove_extension(Util::base_name(ctx.args_info.output_obj));
-    std::string gcda_path = fmt::format("{}/{}.gcda", dir, stem);
-    log("Hashing coverage path {}", gcda_path);
+    std::string gcda_path = FMT("{}/{}.gcda", dir, stem);
+    LOG("Hashing coverage path {}", gcda_path);
     hash.hash_delimiter("gcda");
     hash.hash(gcda_path);
   }
 
   // Possibly hash the sanitize blacklist file path.
   for (const auto& sanitize_blacklist : args_info.sanitize_blacklists) {
-    log("Hashing sanitize blacklist {}", sanitize_blacklist);
+    LOG("Hashing sanitize blacklist {}", sanitize_blacklist);
     hash.hash("sanitizeblacklist");
     if (!hash_binary_file(ctx, hash, sanitize_blacklist)) {
       throw Failure(Statistic::error_hashing_extra_file);
@@ -1412,7 +1364,7 @@ hash_common_info(const Context& ctx,
   if (!ctx.config.extra_files_to_hash().empty()) {
     for (const std::string& path : Util::split_into_strings(
            ctx.config.extra_files_to_hash(), PATH_DELIM)) {
-      log("Hashing extra file {}", path);
+      LOG("Hashing extra file {}", path);
       hash.hash_delimiter("extrafile");
       if (!hash_binary_file(ctx, hash, path)) {
         throw Failure(Statistic::error_hashing_extra_file);
@@ -1421,7 +1373,7 @@ hash_common_info(const Context& ctx,
   }
 
   // Possibly hash GCC_COLORS (for color diagnostics).
-  if (ctx.guessed_compiler == GuessedCompiler::gcc) {
+  if (ctx.config.compiler_type() == CompilerType::gcc) {
     const char* gcc_colors = getenv("GCC_COLORS");
     if (gcc_colors) {
       hash.hash_delimiter("gcccolors");
@@ -1440,23 +1392,23 @@ hash_profile_data_file(const Context& ctx, Hash& hash)
 
   std::vector<std::string> paths_to_try{
     // -fprofile-use[=dir]/-fbranch-probabilities (GCC <9)
-    fmt::format("{}/{}.gcda", profile_path, base_name),
+    FMT("{}/{}.gcda", profile_path, base_name),
     // -fprofile-use[=dir]/-fbranch-probabilities (GCC >=9)
-    fmt::format("{}/{}#{}.gcda", profile_path, hashified_cwd, base_name),
+    FMT("{}/{}#{}.gcda", profile_path, hashified_cwd, base_name),
     // -fprofile(-instr|-sample)-use=file (Clang), -fauto-profile=file (GCC >=5)
     profile_path,
     // -fprofile(-instr|-sample)-use=dir (Clang)
-    fmt::format("{}/default.profdata", profile_path),
+    FMT("{}/default.profdata", profile_path),
     // -fauto-profile (GCC >=5)
     "fbdata.afdo", // -fprofile-dir is not used
   };
 
   bool found = false;
   for (const std::string& p : paths_to_try) {
-    log("Checking for profile data file {}", p);
+    LOG("Checking for profile data file {}", p);
     auto st = Stat::stat(p);
     if (st && !st.is_directory()) {
-      log("Adding profile data {} to the hash", p);
+      LOG("Adding profile data {} to the hash", p);
       hash.hash_delimiter("-fprofile-use");
       if (hash_binary_file(ctx, hash, p)) {
         found = true;
@@ -1502,17 +1454,17 @@ calculate_result_name(Context& ctx,
 
   // clang will emit warnings for unused linker flags, so we shouldn't skip
   // those arguments.
-  int is_clang = ctx.guessed_compiler == GuessedCompiler::clang
-                 || ctx.guessed_compiler == GuessedCompiler::unknown;
+  int is_clang = ctx.config.compiler_type() == CompilerType::clang
+                 || ctx.config.compiler_type() == CompilerType::other;
 
   // First the arguments.
   for (size_t i = 1; i < args.size(); i++) {
     // Trust the user if they've said we should not hash a given option.
     if (option_should_be_ignored(args[i], ctx.ignore_options())) {
-      log("Not hashing ignored option: {}", args[i]);
+      LOG("Not hashing ignored option: {}", args[i]);
       if (i + 1 < args.size() && compopt_takes_arg(args[i])) {
         i++;
-        log("Not hashing argument of ignored option: {}", args[i]);
+        LOG("Not hashing argument of ignored option: {}", args[i]);
       }
       continue;
     }
@@ -1678,13 +1630,13 @@ calculate_result_name(Context& ctx,
 
   if (ctx.args_info.profile_generate) {
     ASSERT(!ctx.args_info.profile_path.empty());
-    log("Adding profile directory {} to our hash", ctx.args_info.profile_path);
+    LOG("Adding profile directory {} to our hash", ctx.args_info.profile_path);
     hash.hash_delimiter("-fprofile-dir");
     hash.hash(ctx.args_info.profile_path);
   }
 
   if (ctx.args_info.profile_use && !hash_profile_data_file(ctx, hash)) {
-    log("No profile data file found");
+    LOG_RAW("No profile data file found");
     throw Failure(Statistic::no_input_file);
   }
 
@@ -1731,7 +1683,7 @@ calculate_result_name(Context& ctx,
       throw Failure(Statistic::internal_error);
     }
     if (result & HASH_SOURCE_CODE_FOUND_TIME) {
-      log("Disabling direct mode");
+      LOG_RAW("Disabling direct mode");
       ctx.config.set_direct_mode(false);
       return nullopt;
     }
@@ -1744,28 +1696,28 @@ calculate_result_name(Context& ctx,
     ctx.set_manifest_path(manifest_file.path);
 
     if (manifest_file.stat) {
-      log("Looking for result name in {}", manifest_file.path);
+      LOG("Looking for result name in {}", manifest_file.path);
       MTR_BEGIN("manifest", "manifest_get");
       result_name = Manifest::get(ctx, manifest_file.path);
       MTR_END("manifest", "manifest_get");
       if (result_name) {
-        log("Got result name from manifest");
+        LOG_RAW("Got result name from manifest");
       } else {
-        log("Did not find result name in manifest");
+        LOG_RAW("Did not find result name in manifest");
       }
     } else {
-      log("No manifest with name {} in the cache", manifest_name.to_string());
+      LOG("No manifest with name {} in the cache", manifest_name.to_string());
     }
   } else {
     if (ctx.args_info.arch_args.empty()) {
       result_name = get_result_name_from_cpp(ctx, preprocessor_args, hash);
-      log("Got result name from preprocessor");
+      LOG_RAW("Got result name from preprocessor");
     } else {
       preprocessor_args.push_back("-arch");
       for (size_t i = 0; i < ctx.args_info.arch_args.size(); ++i) {
         preprocessor_args.push_back(ctx.args_info.arch_args[i]);
         result_name = get_result_name_from_cpp(ctx, preprocessor_args, hash);
-        log("Got result name from preprocessor with -arch {}",
+        LOG("Got result name from preprocessor with -arch {}",
             ctx.args_info.arch_args[i]);
         if (i != ctx.args_info.arch_args.size() - 1) {
           result_name = nullopt;
@@ -1799,11 +1751,11 @@ from_cache(Context& ctx, FromCacheCallMode mode)
   //
   //     file 'foo.h' has been modified since the precompiled header 'foo.pch'
   //     was built
-  if ((ctx.guessed_compiler == GuessedCompiler::clang
-       || ctx.guessed_compiler == GuessedCompiler::unknown)
+  if ((ctx.config.compiler_type() == CompilerType::clang
+       || ctx.config.compiler_type() == CompilerType::other)
       && ctx.args_info.output_is_precompiled_header
       && !ctx.args_info.fno_pch_timestamp && mode == FromCacheCallMode::cpp) {
-    log("Not considering cached precompiled header in preprocessor mode");
+    LOG_RAW("Not considering cached precompiled header in preprocessor mode");
     return nullopt;
   }
 
@@ -1813,7 +1765,7 @@ from_cache(Context& ctx, FromCacheCallMode mode)
   const auto result_file = look_up_cache_file(
     ctx.config.cache_dir(), *ctx.result_name(), Result::k_file_suffix);
   if (!result_file.stat) {
-    log("No result with name {} in the cache", ctx.result_name()->to_string());
+    LOG("No result with name {} in the cache", ctx.result_name()->to_string());
     return nullopt;
   }
   ctx.set_result_path(result_file.path);
@@ -1824,14 +1776,14 @@ from_cache(Context& ctx, FromCacheCallMode mode)
   auto error = result_reader.read(result_retriever);
   MTR_END("cache", "from_cache");
   if (error) {
-    log("Failed to get result from cache: {}", *error);
+    LOG("Failed to get result from cache: {}", *error);
     return nullopt;
   }
 
   // Update modification timestamp to save file from LRU cleanup.
   Util::update_mtime(*ctx.result_path());
 
-  log("Succeeded getting cached result");
+  LOG_RAW("Succeeded getting cached result");
 
   return mode == FromCacheCallMode::direct ? Statistic::direct_cache_hit
                                            : Statistic::preprocessed_cache_hit;
@@ -1844,18 +1796,24 @@ void
 find_compiler(Context& ctx,
               const FindExecutableFunction& find_executable_function)
 {
-  const nonstd::string_view first_param_base_name =
-    Util::base_name(ctx.orig_args[0]);
-  const bool first_param_is_ccache =
-    Util::same_program_name(first_param_base_name, CCACHE_NAME);
+  // gcc --> 0
+  // ccache gcc --> 1
+  // ccache ccache gcc --> 2
+  size_t compiler_pos = 0;
+  while (compiler_pos < ctx.orig_args.size()
+         && Util::same_program_name(
+           Util::base_name(ctx.orig_args[compiler_pos]), CCACHE_NAME)) {
+    ++compiler_pos;
+  }
 
   // Support user override of the compiler.
   const std::string compiler =
     !ctx.config.compiler().empty()
       ? ctx.config.compiler()
-      : (first_param_is_ccache ? ctx.orig_args[1]
-                               // ccache is masquerading as compiler:
-                               : std::string(first_param_base_name));
+      // In case ccache is masquerading as the compiler, use only base_name so
+      // the real compiler can be determined.
+      : (compiler_pos == 0 ? std::string(Util::base_name(ctx.orig_args[0]))
+                           : ctx.orig_args[compiler_pos]);
 
   const std::string resolved_compiler =
     Util::is_full_path(compiler)
@@ -1873,9 +1831,7 @@ find_compiler(Context& ctx,
       CCACHE_NAME);
   }
 
-  if (first_param_is_ccache) {
-    ctx.orig_args.pop_front();
-  }
+  ctx.orig_args.pop_front(compiler_pos);
   ctx.orig_args[0] = resolved_compiler;
 }
 
@@ -1922,9 +1878,9 @@ set_up_config(Config& config)
     // Only used for ccache tests:
     const char* const env_ccache_configpath2 = getenv("CCACHE_CONFIGPATH2");
 
-    config.set_secondary_config_path(
-      env_ccache_configpath2 ? env_ccache_configpath2
-                             : fmt::format("{}/ccache.conf", SYSCONFDIR));
+    config.set_secondary_config_path(env_ccache_configpath2
+                                       ? env_ccache_configpath2
+                                       : FMT("{}/ccache.conf", SYSCONFDIR));
     MTR_BEGIN("config", "conf_read_secondary");
     // A missing config file in SYSCONFDIR is OK so don't check return value.
     config.update_from_file(config.secondary_config_path());
@@ -1939,7 +1895,7 @@ set_up_config(Config& config)
     } else if (legacy_ccache_dir_exists) {
       primary_config_dir = legacy_ccache_dir;
     } else if (env_xdg_config_home) {
-      primary_config_dir = fmt::format("{}/ccache", env_xdg_config_home);
+      primary_config_dir = FMT("{}/ccache", env_xdg_config_home);
     } else {
       primary_config_dir = default_config_dir(home_dir);
     }
@@ -1964,7 +1920,7 @@ set_up_config(Config& config)
     if (legacy_ccache_dir_exists) {
       config.set_cache_dir(legacy_ccache_dir);
     } else if (env_xdg_cache_home) {
-      config.set_cache_dir(fmt::format("{}/ccache", env_xdg_cache_home));
+      config.set_cache_dir(FMT("{}/ccache", env_xdg_cache_home));
     } else {
       config.set_cache_dir(default_cache_dir(home_dir));
     }
@@ -2003,14 +1959,14 @@ initialize(Context& ctx, int argc, const char* const* argv)
     ctx.original_umask = umask(ctx.config.umask());
   }
 
-  log("=== CCACHE {} STARTED =========================================",
+  LOG("=== CCACHE {} STARTED =========================================",
       CCACHE_VERSION);
 
   if (getenv("CCACHE_INTERNAL_TRACE")) {
 #ifdef MTR_ENABLED
     ctx.mini_trace = std::make_unique<MiniTrace>(ctx.args_info);
 #else
-    log("Error: tracing is not enabled!");
+    LOG_RAW("Error: tracing is not enabled!");
 #endif
   }
 }
@@ -2023,11 +1979,11 @@ set_up_uncached_err()
   int uncached_fd =
     dup(STDERR_FILENO); // The file descriptor is intentionally leaked.
   if (uncached_fd == -1) {
-    log("dup(2) failed: {}", strerror(errno));
+    LOG("dup(2) failed: {}", strerror(errno));
     throw Failure(Statistic::internal_error);
   }
 
-  Util::setenv("UNCACHED_ERR_FD", fmt::format("{}", uncached_fd));
+  Util::setenv("UNCACHED_ERR_FD", FMT("{}", uncached_fd));
 }
 
 static void
@@ -2035,7 +1991,7 @@ configuration_logger(const std::string& key,
                      const std::string& value,
                      const std::string& origin)
 {
-  Logging::bulk_log("Config: ({}) {} = {}", origin, key, value);
+  BULK_LOG("Config: ({}) {} = {}", origin, key, value);
 }
 
 static void
@@ -2043,7 +1999,7 @@ configuration_printer(const std::string& key,
                       const std::string& value,
                       const std::string& origin)
 {
-  fmt::print("({}) {} = {}\n", origin, key, value);
+  PRINT(stdout, "({}) {} = {}\n", origin, key, value);
 }
 
 static int cache_compilation(int argc, const char* const* argv);
@@ -2079,12 +2035,12 @@ update_stats_and_maybe_move_cache_file(const Context& ctx,
   const bool use_stats_on_level_1 =
     counter_updates.get(Statistic::cache_size_kibibyte) != 0
     || counter_updates.get(Statistic::files_in_cache) != 0;
-  std::string level_string = fmt::format("{:x}", name.bytes()[0] >> 4);
+  std::string level_string = FMT("{:x}", name.bytes()[0] >> 4);
   if (!use_stats_on_level_1) {
-    level_string += fmt::format("/{:x}", name.bytes()[0] & 0xF);
+    level_string += FMT("/{:x}", name.bytes()[0] & 0xF);
   }
   const auto stats_file =
-    fmt::format("{}/{}/stats", ctx.config.cache_dir(), level_string);
+    FMT("{}/{}/stats", ctx.config.cache_dir(), level_string);
 
   auto counters =
     Statistics::update(stats_file, [&counter_updates](Counters& cs) {
@@ -2104,7 +2060,7 @@ update_stats_and_maybe_move_cache_file(const Context& ctx,
       ctx.config.cache_dir(), wanted_level, name.to_string() + file_suffix);
     if (current_path != wanted_path) {
       Util::ensure_dir_exists(Util::dir_name(wanted_path));
-      log("Moving {} to {}", current_path, wanted_path);
+      LOG("Moving {} to {}", current_path, wanted_path);
       try {
         Util::rename(current_path, wanted_path);
       } catch (const Error&) {
@@ -2123,14 +2079,14 @@ finalize_stats_and_trigger_cleanup(Context& ctx)
 
   if (config.disable()) {
     // Just log result, don't update statistics.
-    log("Result: disabled");
+    LOG_RAW("Result: disabled");
     return;
   }
 
   if (!config.log_file().empty() || config.debug()) {
     const auto result = Statistics::get_result(ctx.counter_updates);
     if (result) {
-      log("Result: {}", *result);
+      LOG("Result: {}", *result);
     }
   }
 
@@ -2145,8 +2101,8 @@ finalize_stats_and_trigger_cleanup(Context& ctx)
     // Context::set_result_path hasn't been called yet, so we just choose one of
     // the stats files in the 256 level 2 directories.
     const auto bucket = getpid() % 256;
-    const auto stats_file = fmt::format(
-      "{}/{:x}/{:x}/stats", config.cache_dir(), bucket / 16, bucket % 16);
+    const auto stats_file =
+      FMT("{}/{:x}/{:x}/stats", config.cache_dir(), bucket / 16, bucket % 16);
     Statistics::update(
       stats_file, [&ctx](Counters& cs) { cs.increment(ctx.counter_updates); });
     return;
@@ -2170,13 +2126,13 @@ finalize_stats_and_trigger_cleanup(Context& ctx)
     return;
   }
 
-  const auto subdir = fmt::format(
-    "{}/{:x}", config.cache_dir(), ctx.result_name()->bytes()[0] >> 4);
+  const auto subdir =
+    FMT("{}/{:x}", config.cache_dir(), ctx.result_name()->bytes()[0] >> 4);
   bool need_cleanup = false;
 
   if (config.max_files() != 0
       && counters->get(Statistic::files_in_cache) > config.max_files() / 16) {
-    log("Need to clean up {} since it holds {} files (limit: {} files)",
+    LOG("Need to clean up {} since it holds {} files (limit: {} files)",
         subdir,
         counters->get(Statistic::files_in_cache),
         config.max_files() / 16);
@@ -2185,7 +2141,7 @@ finalize_stats_and_trigger_cleanup(Context& ctx)
   if (config.max_size() != 0
       && counters->get(Statistic::cache_size_kibibyte)
            > config.max_size() / 1024 / 16) {
-    log("Need to clean up {} since it holds {} KiB (limit: {} KiB)",
+    LOG("Need to clean up {} since it holds {} KiB (limit: {} KiB)",
         subdir,
         counters->get(Statistic::cache_size_kibibyte),
         config.max_size() / 1024 / 16);
@@ -2209,12 +2165,12 @@ finalize_at_exit(Context& ctx)
     finalize_stats_and_trigger_cleanup(ctx);
   } catch (const ErrorBase& e) {
     // finalize_at_exit must not throw since it's called by a destructor.
-    log("Error while finalizing stats: {}", e.what());
+    LOG("Error while finalizing stats: {}", e.what());
   }
 
   // Dump log buffer last to not lose any logs.
   if (ctx.config.debug() && !ctx.args_info.output_obj.empty()) {
-    const auto path = fmt::format("{}.ccache-log", ctx.args_info.output_obj);
+    const auto path = FMT("{}.ccache-log", ctx.args_info.output_obj);
     Logging::dump_log(path);
   }
 }
@@ -2227,6 +2183,7 @@ cache_compilation(int argc, const char* const* argv)
 
   bool fall_back_to_original_compiler = false;
   Args saved_orig_args;
+  nonstd::optional<mode_t> original_umask;
 
   {
     Context ctx;
@@ -2253,25 +2210,26 @@ cache_compilation(int argc, const char* const* argv)
       // Else: Fall back to running the real compiler.
       fall_back_to_original_compiler = true;
 
-      if (ctx.original_umask) {
-        umask(*ctx.original_umask);
-      }
+      original_umask = ctx.original_umask;
 
       ASSERT(!ctx.orig_args.empty());
 
       ctx.orig_args.erase_with_prefix("--ccache-");
       add_prefix(ctx, ctx.orig_args, ctx.config.prefix_command());
 
-      log("Failed; falling back to running the real compiler");
+      LOG_RAW("Failed; falling back to running the real compiler");
 
       saved_orig_args = std::move(ctx.orig_args);
       auto execv_argv = saved_orig_args.to_argv();
-      log("Executing {}", Util::format_argv_for_logging(execv_argv.data()));
+      LOG("Executing {}", Util::format_argv_for_logging(execv_argv.data()));
       // Run execv below after ctx and finalizer have been destructed.
     }
   }
 
   if (fall_back_to_original_compiler) {
+    if (original_umask) {
+      umask(*original_umask);
+    }
     auto execv_argv = saved_orig_args.to_argv();
     execv(execv_argv[0], const_cast<char* const*>(execv_argv.data()));
     throw Fatal("execv of {} failed: {}", execv_argv[0], strerror(errno));
@@ -2284,7 +2242,7 @@ static Statistic
 do_cache_compilation(Context& ctx, const char* const* argv)
 {
   if (ctx.actual_cwd.empty()) {
-    log("Unable to determine current working directory: {}", strerror(errno));
+    LOG("Unable to determine current working directory: {}", strerror(errno));
     throw Failure(Statistic::internal_error);
   }
 
@@ -2298,8 +2256,15 @@ do_cache_compilation(Context& ctx, const char* const* argv)
     ctx.config.visit_items(configuration_logger);
   }
 
+  // Guess compiler after logging the config value in order to be able to
+  // display "compiler_type = auto" before overwriting the value with the guess.
+  if (ctx.config.compiler_type() == CompilerType::auto_guess) {
+    ctx.config.set_compiler_type(guess_compiler(ctx.orig_args[0]));
+  }
+  DEBUG_ASSERT(ctx.config.compiler_type() != CompilerType::auto_guess);
+
   if (ctx.config.disable()) {
-    log("ccache is disabled");
+    LOG_RAW("ccache is disabled");
     // Statistic::cache_miss is a dummy to trigger stats_flush.
     throw Failure(Statistic::cache_miss);
   }
@@ -2308,19 +2273,14 @@ do_cache_compilation(Context& ctx, const char* const* argv)
   set_up_uncached_err();
   MTR_END("main", "set_up_uncached_err");
 
-  log("Command line: {}", Util::format_argv_for_logging(argv));
-  log("Hostname: {}", Util::get_hostname());
-  log("Working directory: {}", ctx.actual_cwd);
+  LOG("Command line: {}", Util::format_argv_for_logging(argv));
+  LOG("Hostname: {}", Util::get_hostname());
+  LOG("Working directory: {}", ctx.actual_cwd);
   if (ctx.apparent_cwd != ctx.actual_cwd) {
-    log("Apparent working directory: {}", ctx.apparent_cwd);
+    LOG("Apparent working directory: {}", ctx.apparent_cwd);
   }
 
-  ctx.config.set_limit_multiple(
-    Util::clamp(ctx.config.limit_multiple(), 0.0, 1.0));
-
-  MTR_BEGIN("main", "guess_compiler");
-  ctx.guessed_compiler = guess_compiler(ctx.orig_args[0]);
-  MTR_END("main", "guess_compiler");
+  LOG("Compiler type: {}", compiler_type_to_string(ctx.config.compiler_type()));
 
   MTR_BEGIN("main", "process_args");
   ProcessArgsResult processed = process_args(ctx);
@@ -2334,38 +2294,37 @@ do_cache_compilation(Context& ctx, const char* const* argv)
       && (!ctx.args_info.generating_dependencies
           || ctx.args_info.output_dep == "/dev/null"
           || !ctx.config.run_second_cpp())) {
-    log("Disabling depend mode");
+    LOG_RAW("Disabling depend mode");
     ctx.config.set_depend_mode(false);
   }
 
-  log("Source file: {}", ctx.args_info.input_file);
+  LOG("Source file: {}", ctx.args_info.input_file);
   if (ctx.args_info.generating_dependencies) {
-    log("Dependency file: {}", ctx.args_info.output_dep);
+    LOG("Dependency file: {}", ctx.args_info.output_dep);
   }
   if (ctx.args_info.generating_coverage) {
-    log("Coverage file is being generated");
+    LOG_RAW("Coverage file is being generated");
   }
   if (ctx.args_info.generating_stackusage) {
-    log("Stack usage file: {}", ctx.args_info.output_su);
+    LOG("Stack usage file: {}", ctx.args_info.output_su);
   }
   if (ctx.args_info.generating_diagnostics) {
-    log("Diagnostics file: {}", ctx.args_info.output_dia);
+    LOG("Diagnostics file: {}", ctx.args_info.output_dia);
   }
   if (!ctx.args_info.output_dwo.empty()) {
-    log("Split dwarf file: {}", ctx.args_info.output_dwo);
+    LOG("Split dwarf file: {}", ctx.args_info.output_dwo);
   }
 
-  log("Object file: {}", ctx.args_info.output_obj);
+  LOG("Object file: {}", ctx.args_info.output_obj);
   MTR_META_THREAD_NAME(ctx.args_info.output_obj.c_str());
 
   if (ctx.config.debug()) {
-    std::string path =
-      fmt::format("{}.ccache-input-text", ctx.args_info.output_obj);
+    std::string path = FMT("{}.ccache-input-text", ctx.args_info.output_obj);
     File debug_text_file(path, "w");
     if (debug_text_file) {
       ctx.hash_debug_files.push_back(std::move(debug_text_file));
     } else {
-      log("Failed to open {}: {}", path, strerror(errno));
+      LOG("Failed to open {}: {}", path, strerror(errno));
     }
   }
 
@@ -2398,7 +2357,7 @@ do_cache_compilation(Context& ctx, const char* const* argv)
   optional<Digest> result_name;
   optional<Digest> result_name_from_manifest;
   if (ctx.config.direct_mode()) {
-    log("Trying direct lookup");
+    LOG_RAW("Trying direct lookup");
     MTR_BEGIN("hash", "direct_hash");
     Args dummy_args;
     result_name =
@@ -2425,7 +2384,7 @@ do_cache_compilation(Context& ctx, const char* const* argv)
   }
 
   if (ctx.config.read_only_direct()) {
-    log("Read-only direct mode; running real compiler");
+    LOG_RAW("Read-only direct mode; running real compiler");
     throw Failure(Statistic::cache_miss);
   }
 
@@ -2468,9 +2427,9 @@ do_cache_compilation(Context& ctx, const char* const* argv)
       // The best thing here would probably be to remove the hash entry from
       // the manifest. For now, we use a simpler method: just remove the
       // manifest file.
-      log("Hash from manifest doesn't match preprocessor output");
-      log("Likely reason: different CCACHE_BASEDIRs used");
-      log("Removing manifest as a safety measure");
+      LOG_RAW("Hash from manifest doesn't match preprocessor output");
+      LOG_RAW("Likely reason: different CCACHE_BASEDIRs used");
+      LOG_RAW("Removing manifest as a safety measure");
       Util::unlink_safe(*ctx.manifest_path());
 
       put_result_in_manifest = true;
@@ -2487,7 +2446,7 @@ do_cache_compilation(Context& ctx, const char* const* argv)
   }
 
   if (ctx.config.read_only()) {
-    log("Read-only mode; running real compiler");
+    LOG_RAW("Read-only mode; running real compiler");
     throw Failure(Statistic::cache_miss);
   }
 
@@ -2514,6 +2473,7 @@ handle_main_options(int argc, const char* const* argv)
 {
   enum longopts {
     CHECKSUM_FILE,
+    CONFIG_PATH,
     DUMP_MANIFEST,
     DUMP_RESULT,
     EVICT_OLDER_THAN,
@@ -2525,7 +2485,8 @@ handle_main_options(int argc, const char* const* argv)
     {"checksum-file", required_argument, nullptr, CHECKSUM_FILE},
     {"cleanup", no_argument, nullptr, 'c'},
     {"clear", no_argument, nullptr, 'C'},
-    {"directory", no_argument, nullptr, 'd'},
+    {"config-path", required_argument, nullptr, CONFIG_PATH},
+    {"directory", required_argument, nullptr, 'd'},
     {"dump-manifest", required_argument, nullptr, DUMP_MANIFEST},
     {"dump-result", required_argument, nullptr, DUMP_RESULT},
     {"evict-older-than", required_argument, nullptr, EVICT_OLDER_THAN},
@@ -2564,10 +2525,14 @@ handle_main_options(int argc, const char* const* argv)
       Util::read_fd(*fd, [&checksum](const void* data, size_t size) {
         checksum.update(data, size);
       });
-      fmt::print("{:016x}\n", checksum.digest());
+      PRINT(stdout, "{:016x}\n", checksum.digest());
       break;
     }
 
+    case CONFIG_PATH:
+      Util::setenv("CCACHE_CONFIGPATH", arg);
+      break;
+
     case DUMP_MANIFEST:
       return Manifest::dump(arg, stdout) ? 0 : 1;
 
@@ -2576,7 +2541,7 @@ handle_main_options(int argc, const char* const* argv)
       Result::Reader result_reader(arg);
       auto error = result_reader.read(result_dumper);
       if (error) {
-        fmt::print(stderr, "Error: {}\n", *error);
+        PRINT(stderr, "Error: {}\n", *error);
       }
       return error ? EXIT_FAILURE : EXIT_SUCCESS;
     }
@@ -2587,7 +2552,7 @@ handle_main_options(int argc, const char* const* argv)
       clean_old(
         ctx, [&](double progress) { progress_bar.update(progress); }, seconds);
       if (isatty(STDOUT_FILENO)) {
-        fmt::print("\n");
+        PRINT_RAW(stdout, "\n");
       }
       break;
     }
@@ -2597,7 +2562,7 @@ handle_main_options(int argc, const char* const* argv)
       Result::Reader result_reader(arg);
       auto error = result_reader.read(result_extractor);
       if (error) {
-        fmt::print(stderr, "Error: {}\n", *error);
+        PRINT(stderr, "Error: {}\n", *error);
       }
       return error ? EXIT_FAILURE : EXIT_SUCCESS;
     }
@@ -2609,12 +2574,12 @@ handle_main_options(int argc, const char* const* argv)
       } else {
         hash.hash_file(arg);
       }
-      fmt::print("{}\n", hash.digest().to_string());
+      PRINT(stdout, "{}\n", hash.digest().to_string());
       break;
     }
 
     case PRINT_STATS:
-      fmt::print(Statistics::format_machine_readable(ctx.config));
+      PRINT_RAW(stdout, Statistics::format_machine_readable(ctx.config));
       break;
 
     case 'c': // --cleanup
@@ -2623,7 +2588,7 @@ handle_main_options(int argc, const char* const* argv)
       clean_up_all(ctx.config,
                    [&](double progress) { progress_bar.update(progress); });
       if (isatty(STDOUT_FILENO)) {
-        fmt::print("\n");
+        PRINT_RAW(stdout, "\n");
       }
       break;
     }
@@ -2633,7 +2598,7 @@ handle_main_options(int argc, const char* const* argv)
       ProgressBar progress_bar("Clearing...");
       wipe_all(ctx, [&](double progress) { progress_bar.update(progress); });
       if (isatty(STDOUT_FILENO)) {
-        fmt::print("\n");
+        PRINT_RAW(stdout, "\n");
       }
       break;
     }
@@ -2643,11 +2608,11 @@ handle_main_options(int argc, const char* const* argv)
       break;
 
     case 'h': // --help
-      fmt::print(stdout, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
+      PRINT(stdout, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
       exit(EXIT_SUCCESS);
 
     case 'k': // --get-config
-      fmt::print("{}\n", ctx.config.get_string_value(arg));
+      PRINT(stdout, "{}\n", ctx.config.get_string_value(arg));
       break;
 
     case 'F': { // --max-files
@@ -2655,9 +2620,9 @@ handle_main_options(int argc, const char* const* argv)
       Config::set_value_in_file(
         ctx.config.primary_config_path(), "max_files", arg);
       if (files == 0) {
-        fmt::print("Unset cache file limit\n");
+        PRINT_RAW(stdout, "Unset cache file limit\n");
       } else {
-        fmt::print("Set cache file limit to {}\n", files);
+        PRINT(stdout, "Set cache file limit to {}\n", files);
       }
       break;
     }
@@ -2667,10 +2632,11 @@ handle_main_options(int argc, const char* const* argv)
       Config::set_value_in_file(
         ctx.config.primary_config_path(), "max_size", arg);
       if (size == 0) {
-        fmt::print("Unset cache size limit\n");
+        PRINT_RAW(stdout, "Unset cache size limit\n");
       } else {
-        fmt::print("Set cache size limit to {}\n",
-                   Util::format_human_readable_size(size));
+        PRINT(stdout,
+              "Set cache size limit to {}\n",
+              Util::format_human_readable_size(size));
       }
       break;
     }
@@ -2693,11 +2659,11 @@ handle_main_options(int argc, const char* const* argv)
       break;
 
     case 's': // --show-stats
-      fmt::print(Statistics::format_human_readable(ctx.config));
+      PRINT_RAW(stdout, Statistics::format_human_readable(ctx.config));
       break;
 
     case 'V': // --version
-      fmt::print(VERSION_TEXT, CCACHE_NAME, CCACHE_VERSION);
+      PRINT(VERSION_TEXT, CCACHE_NAME, CCACHE_VERSION);
       exit(EXIT_SUCCESS);
 
     case 'x': // --show-compression
@@ -2727,11 +2693,11 @@ handle_main_options(int argc, const char* const* argv)
 
     case 'z': // --zero-stats
       Statistics::zero_all_counters(ctx.config);
-      fmt::print("Statistics zeroed\n");
+      PRINT_RAW(stdout, "Statistics zeroed\n");
       break;
 
     default:
-      fmt::print(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
+      PRINT(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
       exit(EXIT_FAILURE);
     }
 
@@ -2754,7 +2720,7 @@ ccache_main(int argc, const char* const* argv)
     std::string program_name(Util::base_name(argv[0]));
     if (Util::same_program_name(program_name, CCACHE_NAME)) {
       if (argc < 2) {
-        fmt::print(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
+        PRINT(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
         exit(EXIT_FAILURE);
       }
       // If the first argument isn't an option, then assume we are being passed
@@ -2766,7 +2732,7 @@ ccache_main(int argc, const char* const* argv)
 
     return cache_compilation(argc, argv);
   } catch (const ErrorBase& e) {
-    fmt::print(stderr, "ccache: error: {}\n", e.what());
+    PRINT(stderr, "ccache: error: {}\n", e.what());
     return EXIT_FAILURE;
   }
 }
index bc8707f..7f833ee 100644 (file)
@@ -21,7 +21,9 @@
 
 #include "system.hpp"
 
-#include "third_party/nonstd/optional.hpp"
+#include "Config.hpp"
+
+#include "third_party/nonstd/string_view.hpp"
 
 #include <functional>
 #include <string>
@@ -30,8 +32,6 @@ class Context;
 
 extern const char CCACHE_VERSION[];
 
-enum class GuessedCompiler { clang, gcc, nvcc, pump, unknown };
-
 const uint32_t SLOPPY_INCLUDE_FILE_MTIME = 1 << 0;
 const uint32_t SLOPPY_INCLUDE_FILE_CTIME = 1 << 1;
 const uint32_t SLOPPY_TIME_MACROS = 1 << 2;
@@ -60,5 +60,4 @@ using FindExecutableFunction =
 // Tested by unit tests.
 void find_compiler(Context& ctx,
                    const FindExecutableFunction& find_executable_function);
-nonstd::optional<std::string>
-rewrite_dep_file_paths(const Context& ctx, const std::string& file_content);
+CompilerType guess_compiler(nonstd::string_view path);
index 3754c32..6d91d77 100644 (file)
@@ -31,8 +31,6 @@
 
 #include <algorithm>
 
-using Logging::log;
-
 static void
 delete_file(const std::string& path,
             uint64_t size,
@@ -41,7 +39,7 @@ delete_file(const std::string& path,
 {
   bool deleted = Util::unlink_safe(path, Util::UnlinkLog::ignore_failure);
   if (!deleted && errno != ENOENT && errno != ESTALE) {
-    log("Failed to unlink {} ({})", path, strerror(errno));
+    LOG("Failed to unlink {} ({})", path, strerror(errno));
   } else if (cache_size && files_in_cache) {
     // The counters are intentionally subtracted even if there was no file to
     // delete since the final cache size calculation will be incorrect if they
@@ -90,7 +88,7 @@ clean_up_dir(const std::string& subdir,
              uint64_t max_age,
              const Util::ProgressReceiver& progress_receiver)
 {
-  log("Cleaning up cache directory {}", subdir);
+  LOG("Cleaning up cache directory {}", subdir);
 
   std::vector<std::shared_ptr<CacheFile>> files;
   Util::get_level_1_files(
@@ -128,7 +126,7 @@ clean_up_dir(const std::string& subdir,
               return f1->lstat().mtime() < f2->lstat().mtime();
             });
 
-  log("Before cleanup: {:.0f} KiB, {:.0f} files",
+  LOG("Before cleanup: {:.0f} KiB, {:.0f} files",
       static_cast<double>(cache_size) / 1024,
       static_cast<double>(files_in_cache));
 
@@ -173,12 +171,12 @@ clean_up_dir(const std::string& subdir,
     cleaned = true;
   }
 
-  log("After cleanup: {:.0f} KiB, {:.0f} files",
+  LOG("After cleanup: {:.0f} KiB, {:.0f} files",
       static_cast<double>(cache_size) / 1024,
       static_cast<double>(files_in_cache));
 
   if (cleaned) {
-    log("Cleaned up cache directory {}", subdir);
+    LOG("Cleaned up cache directory {}", subdir);
   }
 
   update_counters(subdir, files_in_cache, cache_size, cleaned);
@@ -207,7 +205,7 @@ static void
 wipe_dir(const std::string& subdir,
          const Util::ProgressReceiver& progress_receiver)
 {
-  log("Clearing out cache directory {}", subdir);
+  LOG("Clearing out cache directory {}", subdir);
 
   std::vector<std::shared_ptr<CacheFile>> files;
   Util::get_level_1_files(
@@ -220,7 +218,7 @@ wipe_dir(const std::string& subdir,
 
   const bool cleared = !files.empty();
   if (cleared) {
-    log("Cleared out cache directory {}", subdir);
+    LOG("Cleared out cache directory {}", subdir);
   }
   update_counters(subdir, 0, 0, cleared);
 }
index 518d004..1931d5f 100644 (file)
@@ -18,6 +18,8 @@
 
 #include "compopt.hpp"
 
+#include "fmtmacros.hpp"
+
 #include "third_party/fmt/core.h"
 
 // The option it too hard to handle at all.
@@ -185,9 +187,9 @@ compopt_verify_sortedness_and_flags()
 {
   for (size_t i = 0; i < ARRAY_SIZE(compopts); i++) {
     if (compopts[i].type & TOO_HARD && compopts[i].type & TAKES_CONCAT_ARG) {
-      fmt::print(stderr,
-                 "type (TOO_HARD | TAKES_CONCAT_ARG) not allowed, used by {}\n",
-                 compopts[i].name);
+      PRINT(stderr,
+            "type (TOO_HARD | TAKES_CONCAT_ARG) not allowed, used by {}\n",
+            compopts[i].name);
       return false;
     }
 
@@ -196,10 +198,10 @@ compopt_verify_sortedness_and_flags()
     }
 
     if (strcmp(compopts[i - 1].name, compopts[i].name) >= 0) {
-      fmt::print(stderr,
-                 "compopt_verify_sortedness: {} >= {}\n",
-                 compopts[i - 1].name,
-                 compopts[i].name);
+      PRINT(stderr,
+            "compopt_verify_sortedness: {} >= {}\n",
+            compopts[i - 1].name,
+            compopts[i].name);
       return false;
     }
   }
index 1e0982f..42e0179 100644 (file)
 #include "ThreadPool.hpp"
 #include "ZstdCompressor.hpp"
 #include "assertions.hpp"
+#include "fmtmacros.hpp"
 
 #include "third_party/fmt/core.h"
 
 #include <string>
 #include <thread>
 
-using Logging::log;
 using nonstd::optional;
 
 namespace {
@@ -170,9 +170,9 @@ recompress_file(RecompressionStatistics& statistics,
     return;
   }
 
-  log("Recompressing {} to {}",
+  LOG("Recompressing {} to {}",
       cache_file.path(),
-      level ? fmt::format("level {}", wanted_level) : "uncompressed");
+      level ? FMT("level {}", wanted_level) : "uncompressed");
   AtomicFile atomic_new_file(cache_file.path(), AtomicFile::Mode::binary);
   auto writer =
     create_writer(atomic_new_file.stream(),
@@ -203,7 +203,7 @@ recompress_file(RecompressionStatistics& statistics,
 
   statistics.update(content_size, old_stat.size(), new_stat.size(), 0);
 
-  log("Recompression of {} done", cache_file.path());
+  LOG("Recompression of {} done", cache_file.path());
 }
 
 } // namespace
@@ -246,7 +246,7 @@ compress_stats(const Config& config,
     progress_receiver);
 
   if (isatty(STDOUT_FILENO)) {
-    fmt::print("\n\n");
+    PRINT_RAW(stdout, "\n\n");
   }
 
   double ratio =
@@ -260,17 +260,20 @@ compress_stats(const Config& config,
   std::string content_size_str = Util::format_human_readable_size(content_size);
   std::string incompr_size_str = Util::format_human_readable_size(incompr_size);
 
-  fmt::print("Total data:            {:>8s} ({} disk blocks)\n",
-             cache_size_str,
-             on_disk_size_str);
-  fmt::print("Compressed data:       {:>8s} ({:.1f}% of original size)\n",
-             compr_size_str,
-             100.0 - savings);
-  fmt::print("  - Original data:     {:>8s}\n", content_size_str);
-  fmt::print("  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
-             ratio,
-             savings);
-  fmt::print("Incompressible data:   {:>8s}\n", incompr_size_str);
+  PRINT(stdout,
+        "Total data:            {:>8s} ({} disk blocks)\n",
+        cache_size_str,
+        on_disk_size_str);
+  PRINT(stdout,
+        "Compressed data:       {:>8s} ({:.1f}% of original size)\n",
+        compr_size_str,
+        100.0 - savings);
+  PRINT(stdout, "  - Original data:     {:>8s}\n", content_size_str);
+  PRINT(stdout,
+        "  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
+        ratio,
+        savings);
+  PRINT(stdout, "Incompressible data:   {:>8s}\n", incompr_size_str);
 }
 
 void
@@ -322,7 +325,7 @@ compress_recompress(Context& ctx,
     progress_receiver);
 
   if (isatty(STDOUT_FILENO)) {
-    fmt::print("\n\n");
+    PRINT_RAW(stdout, "\n\n");
   }
 
   double old_ratio =
@@ -347,23 +350,27 @@ compress_recompress(Context& ctx,
   std::string incompr_size_str =
     Util::format_human_readable_size(statistics.incompressible_size());
   std::string size_difference_str =
-    fmt::format("{}{}",
-                size_difference < 0 ? "-" : (size_difference > 0 ? "+" : " "),
-                Util::format_human_readable_size(
-                  size_difference < 0 ? -size_difference : size_difference));
-
-  fmt::print("Original data:         {:>8s}\n", content_size_str);
-  fmt::print("Old compressed data:   {:>8s} ({:.1f}% of original size)\n",
-             old_compr_size_str,
-             100.0 - old_savings);
-  fmt::print("  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
-             old_ratio,
-             old_savings);
-  fmt::print("New compressed data:   {:>8s} ({:.1f}% of original size)\n",
-             new_compr_size_str,
-             100.0 - new_savings);
-  fmt::print("  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
-             new_ratio,
-             new_savings);
-  fmt::print("Size change:          {:>9s}\n", size_difference_str);
+    FMT("{}{}",
+        size_difference < 0 ? "-" : (size_difference > 0 ? "+" : " "),
+        Util::format_human_readable_size(
+          size_difference < 0 ? -size_difference : size_difference));
+
+  PRINT(stdout, "Original data:         {:>8s}\n", content_size_str);
+  PRINT(stdout,
+        "Old compressed data:   {:>8s} ({:.1f}% of original size)\n",
+        old_compr_size_str,
+        100.0 - old_savings);
+  PRINT(stdout,
+        "  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
+        old_ratio,
+        old_savings);
+  PRINT(stdout,
+        "New compressed data:   {:>8s} ({:.1f}% of original size)\n",
+        new_compr_size_str,
+        100.0 - new_savings);
+  PRINT(stdout,
+        "  - Compression ratio: {:>5.3f} x  ({:.1f}% space savings)\n",
+        new_ratio,
+        new_savings);
+  PRINT(stdout, "Size change:          {:>9s}\n", size_difference_str);
 }
index 211d4ba..ec87033 100644 (file)
 #include "Stat.hpp"
 #include "TemporaryFile.hpp"
 #include "Util.hpp"
+#include "fmtmacros.hpp"
 
 #ifdef _WIN32
 #  include "Win32Util.hpp"
 #endif
 
-using Logging::log;
 using nonstd::string_view;
 
 #ifdef _WIN32
@@ -111,7 +111,7 @@ win32execute(const char* path,
   if (args.length() > 8192) {
     TemporaryFile tmp_file(path);
     Util::write_fd(*tmp_file.fd, args.data(), args.length());
-    args = fmt::format("\"@{}\"", tmp_file.path);
+    args = FMT("\"@{}\"", tmp_file.path);
     tmp_file_path = tmp_file.path;
   }
   BOOL ret = CreateProcess(full_path.c_str(),
@@ -133,7 +133,7 @@ win32execute(const char* path,
   }
   if (ret == 0) {
     DWORD error = GetLastError();
-    log("failed to execute {}: {} ({})",
+    LOG("failed to execute {}: {} ({})",
         full_path,
         Win32Util::error_message(error),
         error);
@@ -158,7 +158,7 @@ win32execute(const char* path,
 int
 execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid)
 {
-  log("Executing {}", Util::format_argv_for_logging(argv));
+  LOG("Executing {}", Util::format_argv_for_logging(argv));
 
   {
     SignalHandlerBlocker signal_handler_blocker;
@@ -218,7 +218,7 @@ find_executable(const Context& ctx,
     path = getenv("PATH");
   }
   if (path.empty()) {
-    log("No PATH variable");
+    LOG_RAW("No PATH variable");
     return {};
   }
 
@@ -242,7 +242,7 @@ find_executable_in_path(const std::string& name,
     int ret = SearchPath(
       dir.c_str(), name.c_str(), nullptr, sizeof(namebuf), namebuf, nullptr);
     if (!ret) {
-      std::string exename = fmt::format("{}.exe", name);
+      std::string exename = FMT("{}.exe", name);
       ret = SearchPath(dir.c_str(),
                        exename.c_str(),
                        nullptr,
@@ -256,7 +256,7 @@ find_executable_in_path(const std::string& name,
     }
 #else
     ASSERT(!exclude_name.empty());
-    std::string fname = fmt::format("{}/{}", dir, name);
+    std::string fname = FMT("{}/{}", dir, name);
     auto st1 = Stat::lstat(fname);
     auto st2 = Stat::stat(fname);
     // Look for a normal executable file.
diff --git a/src/fmtmacros.hpp b/src/fmtmacros.hpp
new file mode 100644 (file)
index 0000000..9a017fa
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#pragma once
+
+#include "third_party/fmt/core.h"
+#include "third_party/fmt/format.h"
+
+// Convenience macro for calling `fmt::format` with `FMT_STRING` around the
+// format string literal.
+#define FMT(format_, ...) fmt::format(FMT_STRING(format_), __VA_ARGS__)
+
+// Convenience macro for calling `fmt::print` with `FMT_STRING` around the
+// format string literal.
+#define PRINT(stream_, format_, ...)                                           \
+  fmt::print(stream_, FMT_STRING(format_), __VA_ARGS__)
+
+// Convenience macro for calling `fmt::print` with a message that is not a
+// format string.
+#define PRINT_RAW(stream_, message_) fmt::print(stream_, "{}", message_)
index d37c100..072d821 100644 (file)
 #include "Stat.hpp"
 #include "ccache.hpp"
 #include "execute.hpp"
+#include "fmtmacros.hpp"
 #include "macroskip.hpp"
 
+#include "third_party/blake3/blake3_cpu_supports_avx2.h"
+
 #ifdef INODE_CACHE_SUPPORTED
 #  include "InodeCache.hpp"
 #endif
 #  include "Win32Util.hpp"
 #endif
 
-// With older GCC (libgcc), __builtin_cpu_supports("avx2) returns true if AVX2
-// is supported by the CPU but disabled by the OS. This was fixed in GCC 8, 7.4
-// and 6.5 (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85100).
-//
-// For Clang it seems to be correct if compiler-rt is used as -rtlib, at least
-// as of 3.9 (see https://bugs.llvm.org/show_bug.cgi?id=25510). But if libgcc is
-// used we have the same problem as mentioned above. Unfortunately there doesn't
-// seem to be a way to detect which one is used, or the version of libgcc when
-// used by Clang, so assume that it works with Clang >= 3.9.
-#if !(__GNUC__ >= 8 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 4)                  \
-      || (__GNUC__ == 6 && __GNUC_MINOR__ >= 5) || __clang_major__ > 3         \
-      || (__clang_major__ == 3 && __clang_minor__ >= 9))
-#  undef HAVE_AVX2
-#endif
-
 #ifdef HAVE_AVX2
 #  include <immintrin.h>
 #endif
 
-using Logging::log;
 using nonstd::string_view;
 
 namespace {
@@ -221,7 +208,7 @@ int
 check_for_temporal_macros(string_view str)
 {
 #ifdef HAVE_AVX2
-  if (__builtin_cpu_supports("avx2")) {
+  if (blake3_cpu_supports_avx2()) {
     return check_for_temporal_macros_avx2(str);
   }
 #endif
@@ -246,7 +233,7 @@ hash_source_code_string(const Context& ctx,
   hash.hash(str);
 
   if (result & HASH_SOURCE_CODE_FOUND_DATE) {
-    log("Found __DATE__ in {}", path);
+    LOG("Found __DATE__ in {}", path);
 
     // Make sure that the hash sum changes if the (potential) expansion of
     // __DATE__ changes.
@@ -265,10 +252,10 @@ hash_source_code_string(const Context& ctx,
     // not very useful since the chance that we get a cache hit later the same
     // second should be quite slim... So, just signal back to the caller that
     // __TIME__ has been found so that the direct mode can be disabled.
-    log("Found __TIME__ in {}", path);
+    LOG("Found __TIME__ in {}", path);
   }
   if (result & HASH_SOURCE_CODE_FOUND_TIMESTAMP) {
-    log("Found __TIMESTAMP__ in {}", path);
+    LOG("Found __TIMESTAMP__ in {}", path);
 
     // Make sure that the hash sum changes if the (potential) expansion of
     // __TIMESTAMP__ changes.
@@ -375,12 +362,12 @@ hash_command_output(Hash& hash,
   // Add "echo" command.
   bool using_cmd_exe;
   if (Util::starts_with(adjusted_command, "echo")) {
-    adjusted_command = fmt::format("cmd.exe /c \"{}\"", adjusted_command);
+    adjusted_command = FMT("cmd.exe /c \"{}\"", adjusted_command);
     using_cmd_exe = true;
   } else if (Util::starts_with(adjusted_command, "%compiler%")
              && compiler == "echo") {
     adjusted_command =
-      fmt::format("cmd.exe /c \"{}{}\"", compiler, adjusted_command.substr(10));
+      FMT("cmd.exe /c \"{}{}\"", compiler, adjusted_command.substr(10));
     using_cmd_exe = true;
   } else {
     using_cmd_exe = false;
@@ -397,7 +384,7 @@ hash_command_output(Hash& hash,
   }
 
   auto argv = args.to_argv();
-  log("Executing compiler check command {}",
+  LOG("Executing compiler check command {}",
       Util::format_argv_for_logging(argv.data()));
 
 #ifdef _WIN32
@@ -449,7 +436,7 @@ hash_command_output(Hash& hash,
   int fd = _open_osfhandle((intptr_t)pipe_out[0], O_BINARY);
   bool ok = hash.hash_fd(fd);
   if (!ok) {
-    log("Error hashing compiler check command output: {}", strerror(errno));
+    LOG("Error hashing compiler check command output: {}", strerror(errno));
   }
   WaitForSingleObject(pi.hProcess, INFINITE);
   DWORD exitcode;
@@ -458,7 +445,7 @@ hash_command_output(Hash& hash,
   CloseHandle(pi.hProcess);
   CloseHandle(pi.hThread);
   if (exitcode != 0) {
-    log("Compiler check command returned {}", exitcode);
+    LOG("Compiler check command returned {}", exitcode);
     return false;
   }
   return ok;
@@ -486,7 +473,7 @@ hash_command_output(Hash& hash,
     close(pipefd[1]);
     bool ok = hash.hash_fd(pipefd[0]);
     if (!ok) {
-      log("Error hashing compiler check command output: {}", strerror(errno));
+      LOG("Error hashing compiler check command output: {}", strerror(errno));
     }
     close(pipefd[0]);
 
@@ -496,11 +483,11 @@ hash_command_output(Hash& hash,
       if (result == -1 && errno == EINTR) {
         continue;
       }
-      log("waitpid failed: {}", strerror(errno));
+      LOG("waitpid failed: {}", strerror(errno));
       return false;
     }
     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-      log("Compiler check command returned {}", WEXITSTATUS(status));
+      LOG("Compiler check command returned {}", WEXITSTATUS(status));
       return false;
     }
     return ok;
index 3ed7158..d40110a 100644 (file)
@@ -1,14 +1,11 @@
-set(third_party_source_files base32hex.c format.cpp xxhash.c)
-
+add_library(third_party_lib STATIC base32hex.c format.cpp xxhash.c)
 if(NOT MSVC)
-  list(APPEND third_party_source_files getopt_long.c)
+  target_sources(third_party_lib PRIVATE getopt_long.c)
 else()
-  list(APPEND third_party_source_files win32/getopt.c)
+  target_sources(third_party_lib PRIVATE win32/getopt.c)
   target_compile_definitions(third_party_lib PUBLIC -DSTATIC_GETOPT)
 endif()
 
-add_library(third_party_lib STATIC ${third_party_source_files})
-
 if(ENABLE_TRACING)
   target_sources(third_party_lib PRIVATE minitrace.c)
 endif()
index ed09d58..a75e561 100644 (file)
@@ -1,8 +1,10 @@
-add_library(blake3 STATIC blake3.c blake3_dispatch.c blake3_portable.c)
+add_library(blake3 STATIC blake3.c blake3_dispatch_ccache.c blake3_portable.c)
 
 target_link_libraries(blake3 PRIVATE standard_settings)
 
-if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SIZEOF_VOID_P EQUAL 8)
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SIZEOF_VOID_P EQUAL 8
+   AND NOT (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
+            AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
   set(blake_source_type asm)
   set(blake_suffix "_x86-64_unix.S")
 else()
@@ -15,7 +17,15 @@ include(CheckCCompilerFlag)
 
 function(add_source_if_enabled feature compile_flags)
   string(TOUPPER "have_${blake_source_type}_${feature}" have_feature)
-  if(${blake_source_type} STREQUAL "asm")
+
+  # AVX512 support fails to compile with old Apple Clang versions even though
+  # the compiler accepts the -m flags.
+  if(${feature} STREQUAL "avx512"
+      AND CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"
+      AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0)
+    message(STATUS "Detected unsupported compiler for ${have_feature} - disabled")
+    set(${have_feature} FALSE)
+  elseif(${blake_source_type} STREQUAL "asm")
     check_asm_compiler_flag(${compile_flags} ${have_feature})
   else()
     check_c_compiler_flag(${compile_flags} ${have_feature})
diff --git a/src/third_party/blake3/blake3_cpu_supports_avx2.h b/src/third_party/blake3/blake3_cpu_supports_avx2.h
new file mode 100644 (file)
index 0000000..5d52cae
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef BLAKE3_CPU_SUPPORTS_AVX2_H
+#define BLAKE3_CPU_SUPPORTS_AVX2_H
+
+// This file is a ccache modification to BLAKE3
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+bool blake3_cpu_supports_avx2();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/third_party/blake3/blake3_dispatch_ccache.c b/src/third_party/blake3/blake3_dispatch_ccache.c
new file mode 100644 (file)
index 0000000..86496e7
--- /dev/null
@@ -0,0 +1,10 @@
+// This file is a ccache modification to BLAKE3
+
+#include "blake3_dispatch.c"
+
+#include "blake3_cpu_supports_avx2.h"
+
+bool blake3_cpu_supports_avx2()
+{
+  return get_cpu_features() & AVX2;
+}
index 9444698..acbe6cd 100644 (file)
@@ -4,7 +4,7 @@
 //
 // doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD
 //
-// Copyright (c) 2016-2019 Viktor Kirilov
+// Copyright (c) 2016-2020 Viktor Kirilov
 //
 // Distributed under the MIT Software License
 // See accompanying file LICENSE.txt or copy at
@@ -48,8 +48,8 @@
 
 #define DOCTEST_VERSION_MAJOR 2
 #define DOCTEST_VERSION_MINOR 4
-#define DOCTEST_VERSION_PATCH 0
-#define DOCTEST_VERSION_STR "2.4.0"
+#define DOCTEST_VERSION_PATCH 1
+#define DOCTEST_VERSION_STR "2.4.1"
 
 #define DOCTEST_VERSION                                                                            \
     (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH)
@@ -301,11 +301,15 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
 #define DOCTEST_NOINLINE __declspec(noinline)
 #define DOCTEST_UNUSED
 #define DOCTEST_ALIGNMENT(x)
-#else // MSVC
+#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0)
+#define DOCTEST_NOINLINE
+#define DOCTEST_UNUSED
+#define DOCTEST_ALIGNMENT(x)
+#else
 #define DOCTEST_NOINLINE __attribute__((noinline))
 #define DOCTEST_UNUSED __attribute__((unused))
 #define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x)))
-#endif // MSVC
+#endif
 
 #ifndef DOCTEST_NORETURN
 #define DOCTEST_NORETURN [[noreturn]]
@@ -355,8 +359,20 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
 
 #ifndef DOCTEST_BREAK_INTO_DEBUGGER
 // should probably take a look at https://github.com/scottt/debugbreak
-#ifdef DOCTEST_PLATFORM_MAC
+#ifdef DOCTEST_PLATFORM_LINUX
+#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
+// Break at the location of the failing check if possible
 #define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
+#else
+#include <signal.h>
+#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP)
+#endif
+#elif defined(DOCTEST_PLATFORM_MAC)
+#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
+#else
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0");
+#endif
 #elif DOCTEST_MSVC
 #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
 #elif defined(__MINGW32__)
@@ -365,7 +381,7 @@ extern "C" __declspec(dllimport) void __stdcall DebugBreak();
 DOCTEST_GCC_SUPPRESS_WARNING_POP
 #define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak()
 #else // linux
-#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0)
+#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast<void>(0))
 #endif // linux
 #endif // DOCTEST_BREAK_INTO_DEBUGGER
 
@@ -375,6 +391,9 @@ DOCTEST_GCC_SUPPRESS_WARNING_POP
 #endif // DOCTEST_CONFIG_USE_IOSFWD
 
 #ifdef DOCTEST_CONFIG_USE_STD_HEADERS
+#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
 #include <iosfwd>
 #include <cstddef>
 #include <ostream>
@@ -740,7 +759,6 @@ struct ContextOptions //!OCLINT too many fields
 };
 
 namespace detail {
-#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS)
     template <bool CONDITION, typename TYPE = void>
     struct enable_if
     {};
@@ -748,7 +766,6 @@ namespace detail {
     template <typename TYPE>
     struct enable_if<true, TYPE>
     { typedef TYPE type; };
-#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
 
     // clang-format off
     template<class T> struct remove_reference      { typedef T type; };
@@ -757,6 +774,14 @@ namespace detail {
 
     template<class T> struct remove_const          { typedef T type; };
     template<class T> struct remove_const<const T> { typedef T type; };
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+    template<class T> struct is_enum : public std::is_enum<T> {};
+    template<class T> struct underlying_type : public std::underlying_type<T> {};
+#else
+    // Use compiler intrinsics
+    template<class T> struct is_enum { constexpr static bool value = __is_enum(T); };
+    template<class T> struct underlying_type { typedef __underlying_type(T) type; };
+#endif
     // clang-format on
 
     template <typename T>
@@ -771,12 +796,12 @@ namespace detail {
 
         template<class, class = void>
         struct check {
-            static constexpr auto value = false;
+            static constexpr bool value = false;
         };
 
         template<class T>
         struct check<T, decltype(os() << val<T>(), void())> {
-            static constexpr auto value = true;
+            static constexpr bool value = true;
         };
     } // namespace has_insertion_operator_impl
 
@@ -845,7 +870,7 @@ struct StringMaker<R C::*>
     }
 };
 
-template <typename T>
+template <typename T, typename detail::enable_if<!detail::is_enum<T>::value, bool>::type = true>
 String toString(const DOCTEST_REF_WRAP(T) value) {
     return StringMaker<T>::convert(value);
 }
@@ -872,6 +897,12 @@ DOCTEST_INTERFACE String toString(int long long in);
 DOCTEST_INTERFACE String toString(int long long unsigned in);
 DOCTEST_INTERFACE String toString(std::nullptr_t in);
 
+template <typename T, typename detail::enable_if<detail::is_enum<T>::value, bool>::type = true>
+String toString(const DOCTEST_REF_WRAP(T) value) {
+    typedef typename detail::underlying_type<T>::type UT;
+    return toString(static_cast<UT>(value));
+}
+
 #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
 // see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183
 DOCTEST_INTERFACE String toString(const std::string& in);
@@ -1283,12 +1314,12 @@ namespace detail {
     template <class L, class R> struct RelationalComparator<n, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } };
     // clang-format on
 
-    DOCTEST_BINARY_RELATIONAL_OP(0, eq)
-    DOCTEST_BINARY_RELATIONAL_OP(1, ne)
-    DOCTEST_BINARY_RELATIONAL_OP(2, gt)
-    DOCTEST_BINARY_RELATIONAL_OP(3, lt)
-    DOCTEST_BINARY_RELATIONAL_OP(4, ge)
-    DOCTEST_BINARY_RELATIONAL_OP(5, le)
+    DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq)
+    DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne)
+    DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt)
+    DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt)
+    DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge)
+    DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le)
 
     struct DOCTEST_INTERFACE ResultBuilder : public AssertData
     {
@@ -1415,9 +1446,9 @@ namespace detail {
             } catch(T ex) {                    // NOLINT
                 res = m_translateFunction(ex); //!OCLINT parameter reassignment
                 return true;
-            } catch(...) {} //!OCLINT -  empty catch statement
-#endif                      // DOCTEST_CONFIG_NO_EXCEPTIONS
-            ((void)res);    // to silence -Wunused-parameter
+            } catch(...) {}         //!OCLINT -  empty catch statement
+#endif                              // DOCTEST_CONFIG_NO_EXCEPTIONS
+            static_cast<void>(res); // to silence -Wunused-parameter
             return false;
         }
 
@@ -2183,37 +2214,37 @@ int registerReporter(const char* name, int priority, bool isReporter) {
 
 #ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
 
-#define DOCTEST_WARN_THROWS(...) ((void)0)
-#define DOCTEST_CHECK_THROWS(...) ((void)0)
-#define DOCTEST_REQUIRE_THROWS(...) ((void)0)
-#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0)
-#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0)
-#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0)
-#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0)
-#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0)
-#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0)
-#define DOCTEST_WARN_NOTHROW(...) ((void)0)
-#define DOCTEST_CHECK_NOTHROW(...) ((void)0)
-#define DOCTEST_REQUIRE_NOTHROW(...) ((void)0)
-
-#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
-#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
-#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
-#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
-#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
-#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
-#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0)
+#define DOCTEST_WARN_THROWS(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS(...) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0))
+#define DOCTEST_WARN_NOTHROW(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_NOTHROW(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast<void>(0))
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) (static_cast<void>(0))
 
 #else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
 
@@ -2304,86 +2335,86 @@ int registerReporter(const char* name, int priority, bool isReporter) {
 #define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
 #define DOCTEST_REGISTER_LISTENER(name, priority, reporter)
 
-#define DOCTEST_INFO(x) ((void)0)
-#define DOCTEST_CAPTURE(x) ((void)0)
-#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0)
-#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0)
-#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0)
-#define DOCTEST_MESSAGE(x) ((void)0)
-#define DOCTEST_FAIL_CHECK(x) ((void)0)
-#define DOCTEST_FAIL(x) ((void)0)
-
-#define DOCTEST_WARN(...) ((void)0)
-#define DOCTEST_CHECK(...) ((void)0)
-#define DOCTEST_REQUIRE(...) ((void)0)
-#define DOCTEST_WARN_FALSE(...) ((void)0)
-#define DOCTEST_CHECK_FALSE(...) ((void)0)
-#define DOCTEST_REQUIRE_FALSE(...) ((void)0)
-
-#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0)
-#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0)
-#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0)
-#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0)
-#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0)
-#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0)
-
-#define DOCTEST_WARN_THROWS(...) ((void)0)
-#define DOCTEST_CHECK_THROWS(...) ((void)0)
-#define DOCTEST_REQUIRE_THROWS(...) ((void)0)
-#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0)
-#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0)
-#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0)
-#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0)
-#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0)
-#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0)
-#define DOCTEST_WARN_NOTHROW(...) ((void)0)
-#define DOCTEST_CHECK_NOTHROW(...) ((void)0)
-#define DOCTEST_REQUIRE_NOTHROW(...) ((void)0)
-
-#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
-#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0)
-#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
-#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0)
-#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
-#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
-#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0)
-#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0)
-#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0)
-
-#define DOCTEST_WARN_EQ(...) ((void)0)
-#define DOCTEST_CHECK_EQ(...) ((void)0)
-#define DOCTEST_REQUIRE_EQ(...) ((void)0)
-#define DOCTEST_WARN_NE(...) ((void)0)
-#define DOCTEST_CHECK_NE(...) ((void)0)
-#define DOCTEST_REQUIRE_NE(...) ((void)0)
-#define DOCTEST_WARN_GT(...) ((void)0)
-#define DOCTEST_CHECK_GT(...) ((void)0)
-#define DOCTEST_REQUIRE_GT(...) ((void)0)
-#define DOCTEST_WARN_LT(...) ((void)0)
-#define DOCTEST_CHECK_LT(...) ((void)0)
-#define DOCTEST_REQUIRE_LT(...) ((void)0)
-#define DOCTEST_WARN_GE(...) ((void)0)
-#define DOCTEST_CHECK_GE(...) ((void)0)
-#define DOCTEST_REQUIRE_GE(...) ((void)0)
-#define DOCTEST_WARN_LE(...) ((void)0)
-#define DOCTEST_CHECK_LE(...) ((void)0)
-#define DOCTEST_REQUIRE_LE(...) ((void)0)
-
-#define DOCTEST_WARN_UNARY(...) ((void)0)
-#define DOCTEST_CHECK_UNARY(...) ((void)0)
-#define DOCTEST_REQUIRE_UNARY(...) ((void)0)
-#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0)
-#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0)
-#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0)
+#define DOCTEST_INFO(x) (static_cast<void>(0))
+#define DOCTEST_CAPTURE(x) (static_cast<void>(0))
+#define DOCTEST_ADD_MESSAGE_AT(file, line, x) (static_cast<void>(0))
+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) (static_cast<void>(0))
+#define DOCTEST_ADD_FAIL_AT(file, line, x) (static_cast<void>(0))
+#define DOCTEST_MESSAGE(x) (static_cast<void>(0))
+#define DOCTEST_FAIL_CHECK(x) (static_cast<void>(0))
+#define DOCTEST_FAIL(x) (static_cast<void>(0))
+
+#define DOCTEST_WARN(...) (static_cast<void>(0))
+#define DOCTEST_CHECK(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE(...) (static_cast<void>(0))
+#define DOCTEST_WARN_FALSE(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_FALSE(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_FALSE(...) (static_cast<void>(0))
+
+#define DOCTEST_WARN_MESSAGE(cond, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_MESSAGE(cond, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_MESSAGE(cond, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) (static_cast<void>(0))
+
+#define DOCTEST_WARN_THROWS(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS(...) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0))
+#define DOCTEST_WARN_NOTHROW(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_NOTHROW(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast<void>(0))
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) (static_cast<void>(0))
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) (static_cast<void>(0))
+
+#define DOCTEST_WARN_EQ(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_EQ(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_EQ(...) (static_cast<void>(0))
+#define DOCTEST_WARN_NE(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_NE(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_NE(...) (static_cast<void>(0))
+#define DOCTEST_WARN_GT(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_GT(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_GT(...) (static_cast<void>(0))
+#define DOCTEST_WARN_LT(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_LT(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_LT(...) (static_cast<void>(0))
+#define DOCTEST_WARN_GE(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_GE(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_GE(...) (static_cast<void>(0))
+#define DOCTEST_WARN_LE(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_LE(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_LE(...) (static_cast<void>(0))
+
+#define DOCTEST_WARN_UNARY(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_UNARY(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_UNARY(...) (static_cast<void>(0))
+#define DOCTEST_WARN_UNARY_FALSE(...) (static_cast<void>(0))
+#define DOCTEST_CHECK_UNARY_FALSE(...) (static_cast<void>(0))
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) (static_cast<void>(0))
 
 #endif // DOCTEST_CONFIG_DISABLE
 
@@ -3736,8 +3767,8 @@ namespace {
 
     DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
     void color_to_stream(std::ostream& s, Color::Enum code) {
-        ((void)s);    // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS
-        ((void)code); // for DOCTEST_CONFIG_COLORS_NONE
+        static_cast<void>(s);    // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS
+        static_cast<void>(code); // for DOCTEST_CONFIG_COLORS_NONE
 #ifdef DOCTEST_CONFIG_COLORS_ANSI
         if(g_no_colors ||
            (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false))
@@ -3843,7 +3874,28 @@ namespace detail {
 #ifdef DOCTEST_IS_DEBUGGER_ACTIVE
     bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); }
 #else // DOCTEST_IS_DEBUGGER_ACTIVE
-#ifdef DOCTEST_PLATFORM_MAC
+#ifdef DOCTEST_PLATFORM_LINUX
+    class ErrnoGuard {
+    public:
+        ErrnoGuard() : m_oldErrno(errno) {}
+        ~ErrnoGuard() { errno = m_oldErrno; }
+    private:
+        int m_oldErrno;
+    };
+    // See the comments in Catch2 for the reasoning behind this implementation:
+    // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102
+    bool isDebuggerActive() {
+        ErrnoGuard guard;
+        std::ifstream in("/proc/self/status");
+        for(std::string line; std::getline(in, line);) {
+            static const int PREFIX_LEN = 11;
+            if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) {
+                return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';
+            }
+        }
+        return false;
+    }
+#elif defined(DOCTEST_PLATFORM_MAC)
     // The following function is taken directly from the following technical note:
     // https://developer.apple.com/library/archive/qa/qa1361/_index.html
     // Returns true if the current process is being debugged (either
@@ -5454,25 +5506,28 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP
             separator_to_stream();
             s << std::dec;
 
+            auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1)));
+            auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1)));
+            auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1)));
             const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
-            s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6)
+            s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth)
               << p.numTestCasesPassingFilters << " | "
               << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None :
                                                                           Color::Green)
-              << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed"
+              << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed"
               << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None)
-              << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | ";
+              << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |";
             if(opt.no_skipped_summary == false) {
                 const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;
-                s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped
+                s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped
                   << " skipped" << Color::None;
             }
             s << "\n";
-            s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6)
+            s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth)
               << p.numAsserts << " | "
               << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
-              << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None
-              << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6)
+              << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None
+              << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth)
               << p.numAssertsFailed << " failed" << Color::None << " |\n";
             s << Color::Cyan << "[doctest] " << Color::None
               << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)
index e12d25c..75e2768 100644 (file)
@@ -17,6 +17,31 @@ base_tests() {
     expect_equal_object_files reference_test1.o test1.o
 
     # -------------------------------------------------------------------------
+    TEST "ccache ccache gcc"
+    # E.g. due to some suboptimal setup, scripts etc. Source:
+    # https://github.com/ccache/ccache/issues/686
+
+    $REAL_COMPILER -c -o reference_test1.o test1.c
+
+    $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
+
+    $CCACHE $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
+
+    $CCACHE $CCACHE $CCACHE $COMPILER -c test1.c
+    expect_stat 'cache hit (preprocessed)' 2
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_object_files reference_test1.o test1.o
+
+    # -------------------------------------------------------------------------
     TEST "Version output readable"
 
     # The exact output is not tested, but at least it's something human readable
@@ -754,6 +779,29 @@ b"
     expect_equal_object_files reference_test1.o test1.o
 
     # -------------------------------------------------------------------------
+    TEST "CCACHE_COMPILERTYPE"
+
+    $CCACHE_COMPILE -c test1.c
+    cat >gcc <<EOF
+#!/bin/sh
+EOF
+    chmod +x gcc
+
+    CCACHE_DEBUG=1 $CCACHE ./gcc -c test1.c
+    compiler_type=$(sed -rn 's/.*Compiler type: (.*)/\1/p' test1.o.ccache-log)
+    if [ "$compiler_type" != gcc ]; then
+        test_failed "Compiler type $compiler_type != gcc"
+    fi
+
+    rm test1.o.ccache-log
+
+    CCACHE_COMPILERTYPE=clang CCACHE_DEBUG=1 $CCACHE ./gcc -c test1.c
+    compiler_type=$(sed -rn 's/.*Compiler type: (.*)/\1/p' test1.o.ccache-log)
+    if [ "$compiler_type" != clang ]; then
+        test_failed "Compiler type $compiler_type != clang"
+    fi
+
+    # -------------------------------------------------------------------------
     TEST "CCACHE_PATH"
 
     override_path=`pwd`/override_path
@@ -927,22 +975,26 @@ EOF
     saved_umask=$(umask)
     umask 022
     export CCACHE_UMASK=002
+    export CCACHE_TEMPDIR=$CCACHE_DIR/tmp
 
     cat <<EOF >test.c
 int main() {}
 EOF
 
+    # A cache-miss case which affects the stats file on level 1:
+
     $CCACHE -M 5 >/dev/null
     $CCACHE_COMPILE -MMD -c test.c
     expect_stat 'cache hit (preprocessed)' 0
     expect_stat 'cache miss' 1
-    result_file=$(find $CCACHE_DIR -name '*R')
-    level_2_dir=$(dirname $result_file)
-    level_1_dir=$(dirname $(dirname $result_file))
+    result_file=$(find "$CCACHE_DIR" -name '*R')
+    level_2_dir=$(dirname "$result_file")
+    level_1_dir=$(dirname $(dirname "$result_file"))
     expect_perm test.o -rw-r--r--
     expect_perm test.d -rw-r--r--
     expect_perm "$CCACHE_CONFIGPATH" -rw-rw-r--
     expect_perm "$CCACHE_DIR" drwxrwxr-x
+    expect_perm "$CCACHE_DIR/tmp" drwxrwxr-x
     expect_perm "$level_1_dir" drwxrwxr-x
     expect_perm "$level_1_dir/stats" -rw-rw-r--
     expect_perm "$level_2_dir" drwxrwxr-x
@@ -961,42 +1013,76 @@ EOF
     expect_stat 'called for link' 1
     expect_perm test -rwxr-xr-x
 
+    # A non-cache-miss case which affects the stats file on level 2:
+
+    rm -rf "$CCACHE_DIR"
+
+    $CCACHE_COMPILE --version >/dev/null
+    expect_stat 'no input file' 1
+    stats_file=$(find "$CCACHE_DIR" -name stats)
+    level_2_dir=$(dirname "$stats_file")
+    level_1_dir=$(dirname $(dirname "$stats_file"))
+    expect_perm "$CCACHE_DIR" drwxrwxr-x
+    expect_perm "$level_1_dir" drwxrwxr-x
+    expect_perm "$level_2_dir" drwxrwxr-x
+    expect_perm "$stats_file" -rw-rw-r--
+
     umask $saved_umask
 
     # -------------------------------------------------------------------------
-    TEST "No object file"
+    TEST "No object file due to bad prefix"
 
     cat <<'EOF' >test_no_obj.c
 int test_no_obj;
 EOF
-    cat <<'EOF' >prefix-remove.sh
+    cat <<'EOF' >no-object-prefix
 #!/bin/sh
-"$@"
-[ x$2 = x-fcolor-diagnostics ] && shift
-[ x$2 = x-fdiagnostics-color ] && shift
-[ x$2 = x-std=gnu99 ] && shift
-[ x$3 = x-o ] && rm $4
+# Emulate no object file from the compiler.
 EOF
-    chmod +x prefix-remove.sh
-    CCACHE_PREFIX=`pwd`/prefix-remove.sh $CCACHE_COMPILE -c test_no_obj.c
+    chmod +x no-object-prefix
+    CCACHE_PREFIX=$(pwd)/no-object-prefix $CCACHE_COMPILE -c test_no_obj.c
     expect_stat 'compiler produced no output' 1
 
+    CCACHE_PREFIX=$(pwd)/no-object-prefix $CCACHE_COMPILE -c test1.c
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 0
+    expect_stat 'files in cache' 0
+    expect_stat 'compiler produced no output' 2
+
+    # -------------------------------------------------------------------------
+    TEST "No object file due to -fsyntax-only"
+
+    echo '#warning This triggers a compiler warning' >stderr.c
+
+    $REAL_COMPILER -Wall -c stderr.c -fsyntax-only 2>reference_stderr.txt
+
+    expect_contains reference_stderr.txt "This triggers a compiler warning"
+
+    $CCACHE_COMPILE -Wall -c stderr.c -fsyntax-only 2>stderr.txt
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_text_content reference_stderr.txt stderr.txt
+
+    $CCACHE_COMPILE -Wall -c stderr.c -fsyntax-only 2>stderr.txt
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 1
+    expect_equal_text_content reference_stderr.txt stderr.txt
+
     # -------------------------------------------------------------------------
     TEST "Empty object file"
 
     cat <<'EOF' >test_empty_obj.c
 int test_empty_obj;
 EOF
-    cat <<'EOF' >prefix-empty.sh
+    cat <<'EOF' >empty-object-prefix
 #!/bin/sh
-"$@"
-[ x$2 = x-fcolor-diagnostics ] && shift
-[ x$2 = x-fdiagnostics-color ] && shift
-[ x$2 = x-std=gnu99 ] && shift
-[ x$3 = x-o ] && cp /dev/null $4
+# Emulate empty object file from the compiler.
+touch test_empty_obj.o
 EOF
-    chmod +x prefix-empty.sh
-    CCACHE_PREFIX=`pwd`/prefix-empty.sh $CCACHE_COMPILE -c test_empty_obj.c
+    chmod +x empty-object-prefix
+    CCACHE_PREFIX=`pwd`/empty-object-prefix $CCACHE_COMPILE -c test_empty_obj.c
     expect_stat 'compiler produced empty output' 1
 
     # -------------------------------------------------------------------------
index 7badb4b..64c7d4f 100644 (file)
@@ -1,7 +1,7 @@
-if $COMPILER_TYPE_GCC ; then
+if $COMPILER_TYPE_GCC; then
     color_diagnostics_enable='-fdiagnostics-color'
     color_diagnostics_disable='-fno-diagnostics-color'
-elif $COMPILER_TYPE_CLANG ; then
+elif $COMPILER_TYPE_CLANG; then
     color_diagnostics_enable='-fcolor-diagnostics'
     color_diagnostics_disable='-fno-color-diagnostics'
 fi
@@ -13,17 +13,17 @@ SUITE_color_diagnostics_PROBE() {
     fi
 
     # Probe that real compiler actually supports colored diagnostics.
-    if [[ ! $color_diagnostics_enable || ! $color_diagnostics_disable ]] ; then
+    if [[ ! $color_diagnostics_enable || ! $color_diagnostics_disable ]]; then
         echo "compiler $COMPILER does not support colored diagnostics"
-    elif ! $REAL_COMPILER $color_diagnostics_enable -E - </dev/null >/dev/null 2>&1 ; then
+    elif ! $REAL_COMPILER $color_diagnostics_enable -E - </dev/null >/dev/null 2>&1; then
         echo "compiler $COMPILER (version: $compiler_version) does not support $color_diagnostics_enable"
-    elif ! $REAL_COMPILER $color_diagnostics_disable -E - </dev/null >/dev/null 2>&1 ; then
+    elif ! $REAL_COMPILER $color_diagnostics_disable -E - </dev/null >/dev/null 2>&1; then
         echo "compiler $COMPILER (version: $compiler_version) does not support $color_diagnostics_disable"
     fi
 }
 
 SUITE_color_diagnostics_SETUP() {
-    if $run_second_cpp ; then
+    if $run_second_cpp; then
         export CCACHE_CPP2=1
     else
         export CCACHE_NOCPP2=1
@@ -35,7 +35,7 @@ SUITE_color_diagnostics_SETUP() {
 
 color_diagnostics_expect_color() {
     expect_contains "${1:?}" $'\033['
-    expect_contains <(fgrep 'previous prototype' "$1") $'\033['
+    expect_contains <(fgrep 'Wreturn-type' "$1") $'\033['
     expect_contains <(fgrep 'from preprocessor' "$1") $'\033['
 }
 
@@ -44,20 +44,27 @@ color_diagnostics_expect_no_color() {
 }
 
 color_diagnostics_generate_code() {
-    generate_code "$@"
-    echo '#warning "Warning from preprocessor"' >>"$2"
+    cat <<'EOF' >"$1"
+int stderr(void) { /* Warn about no return statement. */ }
+#warning "Warning from preprocessor"
+EOF
 }
 
 # Heap's permutation algorithm
 color_diagnostics_generate_permutations() {
-    local -i i k="${1:?}-1"
-    if (( k )) ; then
+    local -i i
+    local -i k="${1:?}-1"
+    if ((k)); then
         color_diagnostics_generate_permutations "$k"
-        for (( i = 0 ; i < k ; ++i )) ; do
-            if (( k & 1 )) ; then
-                local tmp=${A[$i]} ; A[$i]=${A[$k]} ; A[$k]=$tmp
+        for ((i = 0; i < k; ++i)); do
+            if ((k & 1)); then
+                local tmp=${A[$i]}
+                A[$i]=${A[$k]}
+                A[$k]=$tmp
             else
-                local tmp=${A[0]} ; A[0]=${A[$k]} ; A[$k]=$tmp
+                local tmp=${A[0]}
+                A[0]=${A[$k]}
+                A[$k]=$tmp
             fi
             color_diagnostics_generate_permutations "$k"
         done
@@ -72,56 +79,73 @@ color_diagnostics_run_on_pty() {
     # script returns early on some platforms (leaving a child process living for
     # a short while) and the output may therefore still be pending. Work around
     # this by sleeping until the output file has content.
-    while [ ! -s "$1" ]; do
+    retries=0
+    while [ ! -s "$1" -a "$retries" -lt 100 ]; do
         sleep 0.1
+        retries=$((retries + 1))
     done
 }
 
 color_diagnostics_test() {
     # -------------------------------------------------------------------------
     TEST "Colored diagnostics automatically disabled when stderr is not a TTY (run_second_cpp=$run_second_cpp)"
-    color_diagnostics_generate_code 1 test1.c
-    $CCACHE_COMPILE -Wmissing-prototypes -c -o test1.o test1.c 2>test1.stderr
+
+    color_diagnostics_generate_code test1.c
+    $CCACHE_COMPILE -Wreturn-type -c -o test1.o test1.c 2>test1.stderr
     color_diagnostics_expect_no_color test1.stderr
 
     # Check that subsequently running on a TTY generates a cache hit.
-    color_diagnostics_run_on_pty test1.output "$CCACHE_COMPILE -Wmissing-prototypes -c -o test1.o test1.c"
+    color_diagnostics_run_on_pty test1.output "$CCACHE_COMPILE -Wreturn-type -c -o test1.o test1.c"
     color_diagnostics_expect_color test1.output
     expect_stat 'cache miss' 1
     expect_stat 'cache hit (preprocessed)' 1
 
     # -------------------------------------------------------------------------
     TEST "Colored diagnostics automatically enabled when stderr is a TTY (run_second_cpp=$run_second_cpp)"
-    color_diagnostics_generate_code 1 test1.c
-    color_diagnostics_run_on_pty test1.output "$CCACHE_COMPILE -Wmissing-prototypes -c -o test1.o test1.c"
+
+    color_diagnostics_generate_code test1.c
+    color_diagnostics_run_on_pty test1.output "$CCACHE_COMPILE -Wreturn-type -c -o test1.o test1.c"
     color_diagnostics_expect_color test1.output
 
     # Check that subsequently running without a TTY generates a cache hit.
-    $CCACHE_COMPILE -Wmissing-prototypes -c -o test1.o test1.c 2>test1.stderr
+    $CCACHE_COMPILE -Wreturn-type -c -o test1.o test1.c 2>test1.stderr
     color_diagnostics_expect_no_color test1.stderr
     expect_stat 'cache miss' 1
     expect_stat 'cache hit (preprocessed)' 1
 
     # -------------------------------------------------------------------------
-    while read -r case ; do
+    if $COMPILER_TYPE_GCC; then
+        TEST "-fcolor-diagnostics not accepted for GCC"
+
+        generate_code 1 test.c
+        if $CCACHE_COMPILE -fcolor-diagnostics -c test.c >&/dev/null; then
+            test_failed "-fcolor-diagnostics unexpectedly accepted by GCC"
+        fi
+    fi
+
+    while read -r case; do
         TEST "Cache object shared across ${case} (run_second_cpp=$run_second_cpp)"
-        color_diagnostics_generate_code 1 test1.c
-        local each ; for each in ${case} ; do
+
+        color_diagnostics_generate_code test1.c
+        local each
+        for each in ${case}; do
             case $each in
                 color,*)
-                    local color_flag=$color_diagnostics_enable color_expect=color
+                    local color_flag=$color_diagnostics_enable
+                    local color_expect=color
                     ;;
                 nocolor,*)
-                    local color_flag=$color_diagnostics_disable color_expect=no_color
+                    local color_flag=$color_diagnostics_disable
+                    local color_expect=no_color
                     ;;
             esac
             case $each in
                 *,tty)
-                    color_diagnostics_run_on_pty test1.output "$CCACHE_COMPILE $color_flag -Wmissing-prototypes -c -o test1.o test1.c"
+                    color_diagnostics_run_on_pty test1.output "$CCACHE_COMPILE $color_flag -Wreturn-type -c -o test1.o test1.c"
                     color_diagnostics_expect_$color_expect test1.output
                     ;;
                 *,notty)
-                    $CCACHE_COMPILE $color_flag -Wmissing-prototypes -c -o test1.o test1.c 2>test1.stderr
+                    $CCACHE_COMPILE $color_flag -Wreturn-type -c -o test1.o test1.c 2>test1.stderr
                     color_diagnostics_expect_$color_expect test1.stderr
                     ;;
             esac
@@ -129,7 +153,7 @@ color_diagnostics_test() {
         expect_stat 'cache miss' 1
         expect_stat 'cache hit (preprocessed)' 3
     done < <(
-        A=( {color,nocolor},{tty,notty} )
+        A=({color,nocolor},{tty,notty})
         color_diagnostics_generate_permutations "${#A[@]}"
     )
 }
index ca48bee..0d9fcc5 100644 (file)
@@ -294,6 +294,23 @@ EOF
     expect_stat 'files in cache' 5
 
     # -------------------------------------------------------------------------
+    TEST "Source file with special characters"
 
-    # TODO: Add more test cases (see direct.bash for inspiration)
+    touch 'file with$special#characters.c'
+    $REAL_COMPILER -MMD -c 'file with$special#characters.c'
+    mv 'file with$special#characters.d' reference.d
+
+    CCACHE_DEPEND=1 $CCACHE_COMPILE -MMD -c 'file with$special#characters.c'
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_content 'file with$special#characters.d' reference.d
+
+    rm 'file with$special#characters.d'
+
+    CCACHE_DEPEND=1 $CCACHE_COMPILE -MMD -c 'file with$special#characters.c'
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+    expect_equal_content 'file with$special#characters.d' reference.d
 }
index 82a3a55..44f959f 100644 (file)
@@ -977,7 +977,7 @@ EOF
     # -------------------------------------------------------------------------
     TEST "Comment in strings"
 
-    echo 'char *comment = " /* \\\\u" "foo" " */";' >comment.c
+    echo 'const char *comment = " /* \\\\u" "foo" " */";' >comment.c
 
     $CCACHE_COMPILE -c comment.c
     expect_stat 'cache hit (direct)' 0
@@ -989,7 +989,7 @@ EOF
     expect_stat 'cache hit (preprocessed)' 0
     expect_stat 'cache miss' 1
 
-    echo 'char *comment = " /* \\\\u" "goo" " */";' >comment.c
+    echo 'const char *comment = " /* \\\\u" "goo" " */";' >comment.c
 
     $CCACHE_COMPILE -c comment.c
     expect_stat 'cache hit (direct)' 1
@@ -1122,4 +1122,22 @@ EOF
     expect_stat 'cache hit (direct)' 2
     expect_stat 'cache hit (preprocessed)' 1
     expect_stat 'cache miss' 1
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_RECACHE doesn't add a new manifest entry"
+
+    $CCACHE_COMPILE -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache miss' 1
+    expect_stat 'files in cache' 2 # result + manifest
+
+    manifest_file=$(find $CCACHE_DIR -name '*M')
+    cp $manifest_file saved.manifest
+
+    CCACHE_RECACHE=1 $CCACHE_COMPILE -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache miss' 2
+    expect_stat 'files in cache' 2
+
+    expect_equal_content $manifest_file saved.manifest
 }
index 664d806..51ddb2a 100644 (file)
@@ -1,7 +1,9 @@
 SUITE_modules_PROBE() {
     if ! $COMPILER_TYPE_CLANG; then
         echo "-fmodules/-fcxx-modules not supported by compiler"
-        return
+    else
+        touch test.c
+        $COMPILER -fmodules test.c -S || echo "compiler does not support modules"
     fi
 }
 
index a05d745..b7abaa2 100644 (file)
@@ -3,6 +3,9 @@ SUITE_profiling_PROBE() {
     if ! $COMPILER -fprofile-generate -c test.c 2>/dev/null; then
         echo "compiler does not support profiling"
     fi
+    if ! $COMPILER -fprofile-generate=data -c test.c 2>/dev/null; then
+        echo "compiler does not support -fprofile-generate=path"
+    fi
     if $COMPILER_TYPE_CLANG && ! command -v llvm-profdata$CLANG_VERSION_SUFFIX >/dev/null; then
         echo "llvm-profdata$CLANG_VERSION_SUFFIX tool not found"
     fi
index 332fbc7..8f8073d 100644 (file)
@@ -3,6 +3,10 @@ SUITE_profiling_clang_PROBE() {
         echo "compiler is not Clang"
     elif ! command -v llvm-profdata$CLANG_VERSION_SUFFIX >/dev/null; then
         echo "llvm-profdata$CLANG_VERSION_SUFFIX tool not found"
+    elif ! $COMPILER -fdebug-prefix-map=x=y -c test.c 2>/dev/null; then
+        echo "compiler does not support -fdebug-prefix-map"
+    elif ! $COMPILER -fprofile-sample-accurate -c test.c 2>/dev/null; then
+        echo "compiler does not support -fprofile-sample-accurate"
     fi
 }
 
index 0ca00d8..fa4418b 100644 (file)
@@ -1,7 +1,7 @@
 SUITE_profiling_hip_clang_PROBE() {
     if ! $COMPILER_TYPE_CLANG; then
         echo "compiler is not Clang"
-    elif ! echo | $COMPILER -x hip --cuda-gpu-arch=gfx900 -nogpulib -c - > /dev/null; then
+    elif ! echo | $COMPILER -x hip --cuda-gpu-arch=gfx900 -nogpulib -c - 2> /dev/null; then
         echo "Hip not supported"
     fi
 }
index fbe70f8..de135f9 100644 (file)
@@ -43,7 +43,7 @@ SUITE_sanitize_blacklist() {
     expect_stat 'files in cache' 4
 
     # -------------------------------------------------------------------------
-    TEST "Compile failed"
+    TEST "Unsuccessful compilation"
 
     if $REAL_COMPILER -c -fsanitize-blacklist=nosuchfile.txt test1.c 2>expected.stderr; then
         test_failed "Expected an error compiling test1.c"
index a6eb4a1..9ec2eb8 100644 (file)
@@ -31,7 +31,7 @@ SUITE_serialize_diagnostics() {
     expect_equal_content expected.dia test.dia
 
     # -------------------------------------------------------------------------
-    TEST "Compile failed"
+    TEST "Unsuccessful compilation"
 
     echo "bad source" >error.c
     if $REAL_COMPILER -c --serialize-diagnostics expected.dia error.c 2>expected.stderr; then
index fb13f59..d8c3805 100644 (file)
@@ -2,6 +2,8 @@ SUITE_split_dwarf_PROBE() {
     touch test.c
     if ! $REAL_COMPILER -c -gsplit-dwarf test.c 2>/dev/null || [ ! -e test.dwo ]; then
         echo "-gsplit-dwarf not supported by compiler"
+    elif ! $COMPILER -fdebug-prefix-map=a=b -c test.c 2>/dev/null; then
+        echo "-fdebug-prefix-map not supported by compiler"
     fi
 }
 
index 79cbdb2..d311fb5 100644 (file)
@@ -18,8 +18,6 @@ CheckOptions:
   - key:             readability-function-size.ParameterThreshold
     value:           7
   - key:             readability-function-size.NestingThreshold
-    value:           6
-  - key:             readability-function-size.NestingThreshold
     value:           999999
   - key:             readability-function-size.VariableThreshold
     value:           999999
index 9be4d85..c82a226 100644 (file)
@@ -8,6 +8,7 @@ set(
   test_Compression.cpp
   test_Config.cpp
   test_Counters.cpp
+  test_Depfile.cpp
   test_FormatNonstdStringView.cpp
   test_Hash.cpp
   test_Lockfile.cpp
index 04c4fe9..fa8f7f0 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "../src/Util.hpp"
 #include "../src/exceptions.hpp"
+#include "../src/fmtmacros.hpp"
 
 namespace TestUtil {
 
@@ -27,12 +28,11 @@ size_t TestContext::m_subdir_counter = 0;
 
 TestContext::TestContext() : m_test_dir(Util::get_actual_cwd())
 {
-  if (!Util::base_name(m_test_dir).starts_with("testdir.")) {
+  if (Util::base_name(Util::dir_name(m_test_dir)) != "testdir") {
     throw Error("TestContext instantiated outside test directory");
   }
   ++m_subdir_counter;
-  std::string subtest_dir =
-    fmt::format("{}/test_{}", m_test_dir, m_subdir_counter);
+  std::string subtest_dir = FMT("{}/test_{}", m_test_dir, m_subdir_counter);
   Util::create_dir(subtest_dir);
   if (chdir(subtest_dir.c_str()) != 0) {
     abort();
index 645fc58..df9bf59 100644 (file)
@@ -17,6 +17,7 @@
 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
 #include "../src/Util.hpp"
+#include "../src/fmtmacros.hpp"
 #include "TestUtil.hpp"
 
 #include "third_party/fmt/core.h"
@@ -34,7 +35,7 @@ main(int argc, char** argv)
   Util::unsetenv("GCC_COLORS"); // Don't confuse argument processing tests.
 
   std::string dir_before = Util::get_actual_cwd();
-  std::string testdir = fmt::format("testdir.{}", getpid());
+  std::string testdir = FMT("testdir/{}", getpid());
   Util::wipe_path(testdir);
   Util::create_dir(testdir);
   TestUtil::check_chdir(testdir);
@@ -47,7 +48,7 @@ main(int argc, char** argv)
     TestUtil::check_chdir(dir_before);
     Util::wipe_path(testdir);
   } else {
-    fmt::print(stderr, "Note: Test data has been left in {}\n", testdir);
+    PRINT(stderr, "Note: Test data has been left in {}\n", testdir);
   }
 
   return result;
index 8c2b9cd..1aea024 100644 (file)
@@ -82,7 +82,7 @@ TEST_CASE("Args::from_gcc_atfile")
 
   Args args;
 
-  SUBCASE("Non-existing file")
+  SUBCASE("Nonexistent file")
   {
     CHECK(Args::from_gcc_atfile("at_file") == nonstd::nullopt);
   }
@@ -226,6 +226,23 @@ TEST_CASE("Args operations")
   Args more_args = Args::from_string("x y");
   Args no_args;
 
+  SUBCASE("erase_last")
+  {
+    Args repeated_args = Args::from_string("one two twotwo one two twotwo");
+
+    repeated_args.erase_last("three");
+    CHECK(repeated_args == Args::from_string("one two twotwo one two twotwo"));
+
+    repeated_args.erase_last("two");
+    CHECK(repeated_args == Args::from_string("one two twotwo one twotwo"));
+
+    repeated_args.erase_last("two");
+    CHECK(repeated_args == Args::from_string("one twotwo one twotwo"));
+
+    repeated_args.erase_last("two");
+    CHECK(repeated_args == Args::from_string("one twotwo one twotwo"));
+  }
+
   SUBCASE("erase_with_prefix")
   {
     args.erase_with_prefix("m");
index 95787db..3661c69 100644 (file)
@@ -20,6 +20,7 @@
 #include "../src/Util.hpp"
 #include "../src/ccache.hpp"
 #include "../src/exceptions.hpp"
+#include "../src/fmtmacros.hpp"
 #include "TestUtil.hpp"
 
 #include "third_party/doctest.h"
@@ -42,6 +43,7 @@ TEST_CASE("Config: default values")
   CHECK(config.cache_dir().empty()); // Set later
   CHECK(config.compiler().empty());
   CHECK(config.compiler_check() == "mtime");
+  CHECK(config.compiler_type() == CompilerType::auto_guess);
   CHECK(config.compression());
   CHECK(config.compression_level() == 0);
   CHECK(config.cpp_extension().empty());
@@ -82,9 +84,9 @@ TEST_CASE("Config::update_from_file")
   Util::setenv("USER", user);
 
 #ifndef _WIN32
-  std::string base_dir = fmt::format("/{0}/foo/{0}", user);
+  std::string base_dir = FMT("/{0}/foo/{0}", user);
 #else
-  std::string base_dir = fmt::format("C:/{0}/foo/{0}", user);
+  std::string base_dir = FMT("C:/{0}/foo/{0}", user);
 #endif
 
   Util::write_file(
@@ -97,6 +99,7 @@ TEST_CASE("Config::update_from_file")
     "  #A comment\n"
     "\t compiler = foo\n"
     "compiler_check = none\n"
+    "compiler_type = pump\n"
     "compression=false\n"
     "compression_level= 2\n"
     "cpp_extension = .foo\n"
@@ -132,16 +135,17 @@ TEST_CASE("Config::update_from_file")
   Config config;
   REQUIRE(config.update_from_file("ccache.conf"));
   CHECK(config.base_dir() == base_dir);
-  CHECK(config.cache_dir() == fmt::format("{0}$/{0}/.ccache", user));
+  CHECK(config.cache_dir() == FMT("{0}$/{0}/.ccache", user));
   CHECK(config.compiler() == "foo");
   CHECK(config.compiler_check() == "none");
+  CHECK(config.compiler_type() == CompilerType::pump);
   CHECK_FALSE(config.compression());
   CHECK(config.compression_level() == 2);
   CHECK(config.cpp_extension() == ".foo");
   CHECK(config.depend_mode());
   CHECK_FALSE(config.direct_mode());
   CHECK(config.disable());
-  CHECK(config.extra_files_to_hash() == fmt::format("a:b c:{}", user));
+  CHECK(config.extra_files_to_hash() == FMT("a:b c:{}", user));
   CHECK(config.file_clone());
   CHECK(config.hard_link());
   CHECK_FALSE(config.hash_dir());
@@ -149,12 +153,12 @@ TEST_CASE("Config::update_from_file")
   CHECK(config.ignore_options() == "-a=* -b");
   CHECK(config.keep_comments_cpp());
   CHECK(config.limit_multiple() == Approx(1.0));
-  CHECK(config.log_file() == fmt::format("{0}{0}", user));
+  CHECK(config.log_file() == FMT("{0}{0}", user));
   CHECK(config.max_files() == 17);
   CHECK(config.max_size() == 123 * 1000 * 1000);
-  CHECK(config.path() == fmt::format("{}.x", user));
+  CHECK(config.path() == FMT("{}.x", user));
   CHECK(config.pch_external_checksum());
-  CHECK(config.prefix_command() == fmt::format("x{}", user));
+  CHECK(config.prefix_command() == FMT("x{}", user));
   CHECK(config.prefix_command_cpp() == "y");
   CHECK(config.read_only());
   CHECK(config.read_only_direct());
@@ -166,7 +170,7 @@ TEST_CASE("Config::update_from_file")
             | SLOPPY_FILE_STAT_MATCHES_CTIME | SLOPPY_SYSTEM_HEADERS
             | SLOPPY_PCH_DEFINES | SLOPPY_CLANG_INDEX_STORE));
   CHECK_FALSE(config.stats());
-  CHECK(config.temporary_dir() == fmt::format("{}_foo", user));
+  CHECK(config.temporary_dir() == FMT("{}_foo", user));
   CHECK(config.umask() == 0777);
 }
 
@@ -366,6 +370,7 @@ TEST_CASE("Config::visit_items")
     "cache_dir = cd\n"
     "compiler = c\n"
     "compiler_check = cc\n"
+    "compiler_type = clang\n"
     "compression = true\n"
     "compression_level = 8\n"
     "cpp_extension = ce\n"
@@ -408,7 +413,7 @@ TEST_CASE("Config::visit_items")
   config.visit_items([&](const std::string& key,
                          const std::string& value,
                          const std::string& origin) {
-    received_items.push_back(fmt::format("({}) {} = {}", origin, key, value));
+    received_items.push_back(FMT("({}) {} = {}", origin, key, value));
   });
 
   std::vector<std::string> expected = {
@@ -421,6 +426,7 @@ TEST_CASE("Config::visit_items")
     "(test.conf) cache_dir = cd",
     "(test.conf) compiler = c",
     "(test.conf) compiler_check = cc",
+    "(test.conf) compiler_type = clang",
     "(test.conf) compression = true",
     "(test.conf) compression_level = 8",
     "(test.conf) cpp_extension = ce",
diff --git a/unittest/test_Depfile.cpp b/unittest/test_Depfile.cpp
new file mode 100644 (file)
index 0000000..b098800
--- /dev/null
@@ -0,0 +1,205 @@
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "../src/Context.hpp"
+#include "../src/Depfile.hpp"
+#include "../src/fmtmacros.hpp"
+#include "TestUtil.hpp"
+
+#include "third_party/doctest.h"
+
+using TestUtil::TestContext;
+
+TEST_SUITE_BEGIN("Depfile");
+
+TEST_CASE("Depfile::escape_filename")
+{
+  CHECK(Depfile::escape_filename("") == "");
+  CHECK(Depfile::escape_filename("foo") == "foo");
+  CHECK(Depfile::escape_filename("foo\\bar") == "foo\\\\bar");
+  CHECK(Depfile::escape_filename("foo#bar") == "foo\\#bar");
+  CHECK(Depfile::escape_filename("foo bar") == "foo\\ bar");
+  CHECK(Depfile::escape_filename("foo\tbar") == "foo\\\tbar");
+  CHECK(Depfile::escape_filename("foo$bar") == "foo$$bar");
+}
+
+TEST_CASE("Depfile::rewrite_paths")
+{
+  Context ctx;
+
+  const auto cwd = ctx.actual_cwd;
+  ctx.has_absolute_include_headers = true;
+
+  const auto content = FMT("foo.o: bar.c {0}/bar.h \\\n {1}/fie.h {0}/fum.h\n",
+                           cwd,
+                           Util::dir_name(cwd));
+
+  SUBCASE("Base directory not in dep file content")
+  {
+    ctx.config.set_base_dir("/foo/bar");
+    CHECK(!Depfile::rewrite_paths(ctx, ""));
+    CHECK(!Depfile::rewrite_paths(ctx, content));
+  }
+
+  SUBCASE("Base directory in dep file content but not matching")
+  {
+    ctx.config.set_base_dir(FMT("{}/other", Util::dir_name(cwd)));
+    CHECK(!Depfile::rewrite_paths(ctx, ""));
+    CHECK(!Depfile::rewrite_paths(ctx, content));
+  }
+
+  SUBCASE("Absolute paths under base directory rewritten")
+  {
+    ctx.config.set_base_dir(cwd);
+    const auto actual = Depfile::rewrite_paths(ctx, content);
+    const auto expected =
+      FMT("foo.o: bar.c ./bar.h \\\n {}/fie.h ./fum.h\n", Util::dir_name(cwd));
+    REQUIRE(actual);
+    CHECK(*actual == expected);
+  }
+}
+
+TEST_CASE("Depfile::tokenize")
+{
+  SUBCASE("Parse empty depfile")
+  {
+    std::vector<std::string> result = Depfile::tokenize("");
+    CHECK(result.size() == 0);
+  }
+
+  SUBCASE("Parse simple depfile")
+  {
+    std::vector<std::string> result =
+      Depfile::tokenize("cat.o: meow meow purr");
+    REQUIRE(result.size() == 4);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+    CHECK(result[2] == "meow");
+    CHECK(result[3] == "purr");
+  }
+
+  SUBCASE("Parse depfile with a dollar sign followed by a dollar sign")
+  {
+    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 dollar sign followed by an alphabet")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat.o: meow$w");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow$w");
+  }
+
+  SUBCASE("Parse depfile with a backslash followed by a number sign or a colon")
+  {
+    std::vector<std::string> result =
+      Depfile::tokenize("cat.o: meow\\# meow\\:");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow#");
+    CHECK(result[2] == "meow:");
+  }
+
+  SUBCASE("Parse depfile with a backslash followed by an alphabet")
+  {
+    std::vector<std::string> result =
+      Depfile::tokenize("cat.o: meow\\w purr\\r");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow\\w");
+    CHECK(result[2] == "purr\\r");
+  }
+
+  SUBCASE("Parse depfile with a backslash followed by a space or a tab")
+  {
+    std::vector<std::string> result =
+      Depfile::tokenize("cat.o: meow\\ meow purr\\\tpurr");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow meow");
+    CHECK(result[2] == "purr\tpurr");
+  }
+
+  SUBCASE("Parse depfile with backslashes followed by a space or a tab")
+  {
+    std::vector<std::string> result =
+      Depfile::tokenize("cat.o: meow\\\\\\ meow purr\\\\ purr");
+    REQUIRE(result.size() == 4);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow\\ meow");
+    CHECK(result[2] == "purr\\");
+    CHECK(result[3] == "purr");
+  }
+
+  SUBCASE("Parse depfile with a backslash newline")
+  {
+    std::vector<std::string> result =
+      Depfile::tokenize("cat.o: meow\\\nmeow\\\n purr\\\n\tpurr");
+    REQUIRE(result.size() == 5);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+    CHECK(result[2] == "meow");
+    CHECK(result[3] == "purr");
+    CHECK(result[4] == "purr");
+  }
+
+  SUBCASE("Parse depfile with a new line")
+  {
+    // This is invalid depfile because it has multiple lines without backslash,
+    // which is not valid in Makefile syntax.
+    // However, Depfile::tokenize is parsing it to each token, which is
+    // expected.
+    std::vector<std::string> result =
+      Depfile::tokenize("cat.o: meow\nmeow\npurr\n");
+    REQUIRE(result.size() == 4);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+    CHECK(result[2] == "meow");
+    CHECK(result[3] == "purr");
+  }
+
+  SUBCASE("Parse depfile with a trailing dollar sign")
+  {
+    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 trailing backslash")
+  {
+    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 trailing backslash newline")
+  {
+    std::vector<std::string> result = Depfile::tokenize("cat.o: meow\\\n");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+  }
+}
+
+TEST_SUITE_END();
index d945848..5d6892c 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "../src/Statistics.hpp"
 #include "../src/Util.hpp"
+#include "../src/fmtmacros.hpp"
 #include "TestUtil.hpp"
 
 #include "third_party/doctest.h"
@@ -66,7 +67,7 @@ TEST_CASE("Read future counters")
   std::string content;
   size_t count = static_cast<size_t>(Statistic::END) + 1;
   for (size_t i = 0; i < count; ++i) {
-    content += fmt::format("{}\n", i);
+    content += FMT("{}\n", i);
   }
 
   Util::write_file("test", content);
index 5633c02..917c137 100644 (file)
@@ -19,6 +19,7 @@
 #include "../src/Config.hpp"
 #include "../src/Fd.hpp"
 #include "../src/Util.hpp"
+#include "../src/fmtmacros.hpp"
 #include "TestUtil.hpp"
 
 #include "third_party/doctest.h"
@@ -910,7 +911,7 @@ TEST_CASE("Util::traverse")
 
   std::vector<std::string> visited;
   auto visitor = [&visited](const std::string& path, bool is_dir) {
-    visited.push_back(fmt::format("[{}] {}", is_dir ? 'd' : 'f', path));
+    visited.push_back(FMT("[{}] {}", is_dir ? 'd' : 'f', path));
   };
 
   SUBCASE("traverse nonexistent path")
@@ -959,7 +960,7 @@ TEST_CASE("Util::wipe_path")
 {
   TestContext test_context;
 
-  SUBCASE("Wipe non-existing path")
+  SUBCASE("Wipe nonexistent path")
   {
     CHECK_NOTHROW(Util::wipe_path("a"));
   }
index c0f349e..7e2cbca 100644 (file)
@@ -21,6 +21,7 @@
 #include "../src/Context.hpp"
 #include "../src/Statistics.hpp"
 #include "../src/Util.hpp"
+#include "../src/fmtmacros.hpp"
 #include "TestUtil.hpp"
 #include "argprocessing.hpp"
 
@@ -69,6 +70,22 @@ get_posix_path(const std::string& path)
 
 TEST_SUITE_BEGIN("argprocessing");
 
+TEST_CASE("pass -fsyntax-only to compiler only")
+{
+  TestContext test_context;
+  Context ctx;
+
+  ctx.orig_args = Args::from_string("cc -c foo.c -fsyntax-only");
+  Util::write_file("foo.c", "");
+
+  const ProcessArgsResult result = process_args(ctx);
+
+  CHECK(!result.error);
+  CHECK(result.preprocessor_args.to_string() == "cc");
+  CHECK(result.extra_args_to_hash.to_string() == "-fsyntax-only");
+  CHECK(result.compiler_args.to_string() == "cc -fsyntax-only -c");
+}
+
 TEST_CASE("dash_E_should_result_in_called_for_preprocessing")
 {
   TestContext test_context;
@@ -283,7 +300,7 @@ TEST_CASE("sysroot_should_be_rewritten_if_basedir_is_used")
   Util::write_file("foo.c", "");
   ctx.config.set_base_dir(get_root());
   std::string arg_string =
-    fmt::format("cc --sysroot={}/foo/bar -c foo.c", ctx.actual_cwd);
+    FMT("cc --sysroot={}/foo/bar -c foo.c", ctx.actual_cwd);
   ctx.orig_args = Args::from_string(arg_string);
 
   const ProcessArgsResult result = process_args(ctx);
@@ -300,8 +317,7 @@ TEST_CASE(
 
   Util::write_file("foo.c", "");
   ctx.config.set_base_dir(get_root());
-  std::string arg_string =
-    fmt::format("cc --sysroot {}/foo -c foo.c", ctx.actual_cwd);
+  std::string arg_string = FMT("cc --sysroot {}/foo -c foo.c", ctx.actual_cwd);
   ctx.orig_args = Args::from_string(arg_string);
 
   const ProcessArgsResult result = process_args(ctx);
@@ -436,8 +452,7 @@ TEST_CASE(
 
   Util::write_file("foo.c", "");
   ctx.config.set_base_dir(get_root());
-  std::string arg_string =
-    fmt::format("cc -isystem {}/foo -c foo.c", ctx.actual_cwd);
+  std::string arg_string = FMT("cc -isystem {}/foo -c foo.c", ctx.actual_cwd);
   ctx.orig_args = Args::from_string(arg_string);
 
   const ProcessArgsResult result = process_args(ctx);
@@ -455,7 +470,7 @@ TEST_CASE("isystem_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used")
   ctx.config.set_base_dir("/"); // posix
   // Windows path doesn't work concatenated.
   std::string cwd = get_posix_path(ctx.actual_cwd);
-  std::string arg_string = fmt::format("cc -isystem{}/foo -c foo.c", cwd);
+  std::string arg_string = FMT("cc -isystem{}/foo -c foo.c", cwd);
   ctx.orig_args = Args::from_string(arg_string);
 
   const ProcessArgsResult result = process_args(ctx);
@@ -473,7 +488,7 @@ TEST_CASE("I_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used")
   ctx.config.set_base_dir("/"); // posix
   // Windows path doesn't work concatenated.
   std::string cwd = get_posix_path(ctx.actual_cwd);
-  std::string arg_string = fmt::format("cc -I{}/foo -c foo.c", cwd);
+  std::string arg_string = FMT("cc -I{}/foo -c foo.c", cwd);
   ctx.orig_args = Args::from_string(arg_string);
 
   const ProcessArgsResult result = process_args(ctx);
@@ -533,7 +548,7 @@ TEST_CASE("cuda_option_file")
 {
   TestContext test_context;
   Context ctx;
-  ctx.guessed_compiler = GuessedCompiler::nvcc;
+  ctx.config.set_compiler_type(CompilerType::nvcc);
   ctx.orig_args = Args::from_string("nvcc -optf foo.optf,bar.optf");
   Util::write_file("foo.c", "");
   Util::write_file("foo.optf", "-c foo.c -g -Wall -o");
index 3011d2f..e6cd6eb 100644 (file)
 // this program; if not, write to the Free Software Foundation, Inc., 51
 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
-#include "Context.hpp"
+#include "../src/Context.hpp"
+#include "../src/ccache.hpp"
+#include "../src/fmtmacros.hpp"
 #include "TestUtil.hpp"
-#include "ccache.hpp"
 
 #include "third_party/doctest.h"
 #include "third_party/nonstd/optional.hpp"
@@ -29,6 +30,8 @@
 #  define CCACHE_NAME "ccache"
 #endif
 
+using TestUtil::TestContext;
+
 TEST_SUITE_BEGIN("ccache");
 
 // Wraps find_compiler in a test friendly interface.
@@ -105,6 +108,16 @@ TEST_CASE("find_compiler")
     CHECK(helper("/abs/" CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc");
   }
 
+  SUBCASE("double ccache")
+  {
+    // E.g. due to some suboptimal setup, scripts etc. Source:
+    // https://github.com/ccache/ccache/issues/686
+    CHECK(helper(CCACHE_NAME " gcc", "") == "resolved_gcc");
+    CHECK(helper(CCACHE_NAME " " CCACHE_NAME " gcc", "") == "resolved_gcc");
+    CHECK(helper(CCACHE_NAME " " CCACHE_NAME " " CCACHE_NAME " gcc", "")
+          == "resolved_gcc");
+  }
+
   SUBCASE("config")
   {
     // In case the first parameter is gcc it must be a link to ccache so use
@@ -144,41 +157,47 @@ TEST_CASE("find_compiler")
   }
 }
 
-TEST_CASE("rewrite_dep_file_paths")
+TEST_CASE("guess_compiler")
 {
-  Context ctx;
+  TestContext test_context;
 
-  const auto cwd = ctx.actual_cwd;
-  ctx.has_absolute_include_headers = true;
-
-  const auto content =
-    fmt::format("foo.o: bar.c {0}/bar.h \\\n {1}/fie.h {0}/fum.h\n",
-                cwd,
-                Util::dir_name(cwd));
-
-  SUBCASE("Base directory not in dep file content")
+  SUBCASE("Compiler not in file system")
   {
-    ctx.config.set_base_dir("/foo/bar");
-    CHECK(!rewrite_dep_file_paths(ctx, ""));
-    CHECK(!rewrite_dep_file_paths(ctx, content));
+    CHECK(guess_compiler("/test/prefix/clang") == CompilerType::clang);
+    CHECK(guess_compiler("/test/prefix/clang-3.8") == CompilerType::clang);
+    CHECK(guess_compiler("/test/prefix/clang++") == CompilerType::clang);
+    CHECK(guess_compiler("/test/prefix/clang++-10") == CompilerType::clang);
+
+    CHECK(guess_compiler("/test/prefix/gcc") == CompilerType::gcc);
+    CHECK(guess_compiler("/test/prefix/gcc-4.8") == CompilerType::gcc);
+    CHECK(guess_compiler("/test/prefix/g++") == CompilerType::gcc);
+    CHECK(guess_compiler("/test/prefix/g++-9") == CompilerType::gcc);
+    CHECK(guess_compiler("/test/prefix/x86_64-w64-mingw32-gcc-posix")
+          == CompilerType::gcc);
+
+    CHECK(guess_compiler("/test/prefix/nvcc") == CompilerType::nvcc);
+    CHECK(guess_compiler("/test/prefix/nvcc-10.1.243") == CompilerType::nvcc);
+
+    CHECK(guess_compiler("/test/prefix/pump") == CompilerType::pump);
+    CHECK(guess_compiler("/test/prefix/distcc-pump") == CompilerType::pump);
+
+    CHECK(guess_compiler("/test/prefix/x") == CompilerType::other);
+    CHECK(guess_compiler("/test/prefix/cc") == CompilerType::other);
+    CHECK(guess_compiler("/test/prefix/c++") == CompilerType::other);
   }
 
-  SUBCASE("Base directory in dep file content but not matching")
+#ifndef _WIN32
+  SUBCASE("Follow symlink to actual compiler")
   {
-    ctx.config.set_base_dir(fmt::format("{}/other", Util::dir_name(cwd)));
-    CHECK(!rewrite_dep_file_paths(ctx, ""));
-    CHECK(!rewrite_dep_file_paths(ctx, content));
-  }
+    const auto cwd = Util::get_actual_cwd();
+    Util::write_file(FMT("{}/gcc", cwd), "");
+    CHECK(symlink("gcc", FMT("{}/intermediate", cwd).c_str()) == 0);
+    const auto cc = FMT("{}/cc", cwd);
+    CHECK(symlink("intermediate", cc.c_str()) == 0);
 
-  SUBCASE("Absolute paths under base directory rewritten")
-  {
-    ctx.config.set_base_dir(cwd);
-    const auto actual = rewrite_dep_file_paths(ctx, content);
-    const auto expected = fmt::format(
-      "foo.o: bar.c ./bar.h \\\n {}/fie.h ./fum.h\n", Util::dir_name(cwd));
-    REQUIRE(actual);
-    CHECK(*actual == expected);
+    CHECK(guess_compiler(cc) == CompilerType::gcc);
   }
+#endif
 }
 
 TEST_SUITE_END();