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()
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:
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
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
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
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 }}
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
- name: Run Clang-Format in check mode
run: misc/format-files --all --check
- env:
- VERBOSE: 1
codespell:
name: Spelling
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)
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)
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
#
# 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.
# 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 "")
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)
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)
# 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)
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()
-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)
--- /dev/null
+# 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)
#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
* Wilson Snyder
* Xavier René-Corail
* Yiding Jia
+* Yoshimasa Niwa
* Yvan Janssens
Thanks!
)
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()
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
- 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.
Name
----
-Ccache - a fast C/C++ compiler cache
+ccache - a fast C/C++ compiler cache
Synopsis
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_::
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).
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
--
--
+[[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
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
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/*
ccache \
clang \
cmake \
+ docbook-xml \
+ docbook-xsl \
elfutils \
gcc-multilib \
libzstd1-dev \
--- /dev/null
+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
ccache \
clang \
cmake \
+ docbook-xml \
+ docbook-xsl \
elfutils \
gcc-multilib \
libzstd-dev \
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
- 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
...
}
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(),
// 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);
// 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;
Context.cpp
Counters.cpp
Decompressor.cpp
+ Depfile.cpp
Hash.cpp
Lockfile.cpp
Logging.cpp
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
#include "Compressor.hpp"
#include "exceptions.hpp"
+#include "fmtmacros.hpp"
#include "third_party/fmt/core.h"
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
#include "assertions.hpp"
#include "ccache.hpp"
#include "exceptions.hpp"
+#include "fmtmacros.hpp"
#include "third_party/fmt/core.h"
cache_dir,
compiler,
compiler_check,
+ compiler_type,
compression,
compression_level,
cpp_extension,
{"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},
{"COMMENTS", "keep_comments_cpp"},
{"COMPILER", "compiler"},
{"COMPILERCHECK", "compiler_check"},
+ {"COMPILERTYPE", "compiler_type"},
{"COMPRESS", "compression"},
{"COMPRESSLEVEL", "compression_level"},
{"CPP2", "run_second_cpp"},
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)
{
if (umask == std::numeric_limits<uint32_t>::max()) {
return {};
} else {
- return fmt::format("{:03o}", umask);
+ return FMT("{:03o}", umask);
}
}
} // 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
{
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;
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);
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();
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;
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:
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";
}
#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
{
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;
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);
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 = "";
return m_compiler_check;
}
+inline CompilerType
+Config::compiler_type() const
+{
+ return m_compiler_type;
+}
+
inline bool
Config::compression() const
{
}
inline void
+Config::set_compiler_type(CompilerType value)
+{
+ m_compiler_type = value;
+}
+
+inline void
Config::set_depend_mode(bool value)
{
m_depend_mode = 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;
#include <string>
#include <vector>
-using Logging::log;
using nonstd::string_view;
Context::Context()
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;
}
}
// 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;
--- /dev/null
+// 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
--- /dev/null
+// 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
#include "Fd.hpp"
#include "Logging.hpp"
+#include "fmtmacros.hpp"
-using Logging::log;
using nonstd::string_view;
const string_view HASH_DELIMITER("\000cCaChE\000", 8);
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;
}
{
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;
}
#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
}
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;
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,
}
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;
}
{
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;
}
{
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;
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.
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);
}
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 =
*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;
}
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) {
// 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;
}
}
release_bucket(bucket);
- log("inode cache {}: {}", found ? "hit" : "miss", path);
+ LOG("inode cache {}: {}", found ? "hit" : "miss", path);
if (m_config.debug()) {
if (found) {
} 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());
release_bucket(bucket);
- log("inode cache insert: {}", path);
+ LOG("inode cache insert: {}", path);
return true;
}
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
#include "Logging.hpp"
#include "Util.hpp"
+#include "fmtmacros.hpp"
#ifdef _WIN32
# include "Win32Util.hpp"
#include <sstream>
#include <thread>
-using Logging::log;
-
namespace {
#ifndef _WIN32
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.
}
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))) {
// 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;
}
DWORD error = GetLastError();
- log("lockfile_acquire: CreateFile {}: {} ({})",
+ LOG("lockfile_acquire: CreateFile {}: {} ({})",
lockfile,
Win32Util::error_message(error),
error);
}
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);
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);
#include "Util.hpp"
#include "exceptions.hpp"
#include "execute.hpp"
+#include "fmtmacros.hpp"
#ifdef HAVE_SYSLOG_H
# include <syslog.h>
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);
}
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));
}
}
#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 {
// 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
#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;
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.
// 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,
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:
// 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);
}
}
}
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) {
// 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;
}
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>();
}
// 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
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;
# include "MiniTrace.hpp"
# include "TemporaryFile.hpp"
# include "Util.hpp"
+# include "fmtmacros.hpp"
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
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);
}
#include "ProgressBar.hpp"
+#include "fmtmacros.hpp"
+
#include "third_party/fmt/core.h"
#ifndef _WIN32
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);
#include "Statistics.hpp"
#include "Util.hpp"
#include "exceptions.hpp"
+#include "fmtmacros.hpp"
#include <algorithm>
//
// 1: Introduced in ccache 4.0.
-using Logging::log;
using nonstd::nullopt;
using nonstd::optional;
using nonstd::string_view;
{
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
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");
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)) {
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),
#include "CacheEntryReader.hpp"
#include "Context.hpp"
#include "Logging.hpp"
+#include "fmtmacros.hpp"
using nonstd::optional;
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
#include "ResultExtractor.hpp"
#include "Util.hpp"
+#include "fmtmacros.hpp"
ResultExtractor::ResultExtractor(const std::string& directory)
: m_directory(directory)
{
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(
#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)
}
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),
// 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) {
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;
}
}
#include "Logging.hpp"
-using Logging::log;
-
Stat::Stat(StatFunction stat_function,
const std::string& path,
Stat::OnError on_error)
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
#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;
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
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));
}
}
}
{
Lockfile lock(path);
if (!lock.acquired()) {
- log("failed to acquire lock for {}", path);
+ LOG("Failed to acquire lock for {}", path);
return nullopt;
}
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();
// 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;
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);
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.
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;
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));
}
}
#include "FormatNonstdStringView.hpp"
#include "Logging.hpp"
#include "TemporaryFile.hpp"
+#include "fmtmacros.hpp"
extern "C" {
#include "third_party/base32hex.h"
#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;
{
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);
}
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);
});
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);
}
}
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);
}
}
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
}
if (ret == -1) {
- log("Failed reading {}", path);
+ LOG("Failed reading {}", path);
throw Error(strerror(errno));
}
#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
}
}
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));
}
}
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));
}
}
// 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);
// 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);
// 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.
#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);
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);
}
#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;
// 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
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;
}
}
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;
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).
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;
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,
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]);
}
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;
}
}
// 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;
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;
}
// 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;
}
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]);
// 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;
// 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;
}
// 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()) {
// 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]);
// 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;
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];
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]);
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;
}
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;
// 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]);
// 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]);
// -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) {
} 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);
}
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;
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;
}
// 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;
}
// 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;
}
// 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;
}
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;
}
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;
}
}
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 {
}
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;
}
}
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;
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;
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
}
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);
}
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);
}
// 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;
}
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;
}
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;
}
}
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;
}
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;
}
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()) {
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};
}
#include "assertions.hpp"
#include "Util.hpp"
+#include "fmtmacros.hpp"
#include "third_party/fmt/core.h"
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();
}
#include "Checksum.hpp"
#include "Compression.hpp"
#include "Context.hpp"
+#include "Depfile.hpp"
#include "Fd.hpp"
#include "File.hpp"
#include "Finalizer.hpp"
#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
version.
)";
-const char USAGE_TEXT[] =
+constexpr const char USAGE_TEXT[] =
R"(Usage:
{} [options]
{} compiler [compiler 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
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]);
}
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
}
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;
}
// 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;
}
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);
}
}
{
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);
}
}
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);
}
}
}
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
// 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);
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>
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) {
return hash.digest();
}
-
// Execute the compiler/preprocessor, with logic to retry without requesting
// colored diagnostics messages if that fails.
static int
{
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);
}
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) {
(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(
"# 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;
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());
}
}
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 {};
// 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;
// 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);
}
}
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));
&& 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);
}
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);
}
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);
// 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);
}
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));
}
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);
}
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);
}
} 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);
}
} 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);
}
#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);
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);
}
}
}
- log("Hashing CWD {}", dir_to_hash);
+ LOG("Hashing CWD {}", dir_to_hash);
hash.hash_delimiter("cwd");
hash.hash(dir_to_hash);
}
}
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);
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);
}
// 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");
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;
// 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;
}
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);
}
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;
}
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;
//
// 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;
}
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);
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;
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)
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;
}
// 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());
} 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);
}
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));
}
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
}
}
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
const std::string& value,
const std::string& origin)
{
- Logging::bulk_log("Config: ({}) {} = {}", origin, key, value);
+ BULK_LOG("Config: ({}) {} = {}", origin, key, value);
}
static void
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);
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) {
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&) {
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);
}
}
// 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;
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);
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);
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);
}
}
bool fall_back_to_original_compiler = false;
Args saved_orig_args;
+ nonstd::optional<mode_t> original_umask;
{
Context ctx;
// 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));
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);
}
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);
}
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);
&& (!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));
}
}
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 =
}
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);
}
// 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;
}
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);
}
{
enum longopts {
CHECKSUM_FILE,
+ CONFIG_PATH,
DUMP_MANIFEST,
DUMP_RESULT,
EVICT_OLDER_THAN,
{"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},
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;
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;
}
clean_old(
ctx, [&](double progress) { progress_bar.update(progress); }, seconds);
if (isatty(STDOUT_FILENO)) {
- fmt::print("\n");
+ PRINT_RAW(stdout, "\n");
}
break;
}
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;
}
} 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
clean_up_all(ctx.config,
[&](double progress) { progress_bar.update(progress); });
if (isatty(STDOUT_FILENO)) {
- fmt::print("\n");
+ PRINT_RAW(stdout, "\n");
}
break;
}
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;
}
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
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;
}
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;
}
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
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);
}
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
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;
}
}
#include "system.hpp"
-#include "third_party/nonstd/optional.hpp"
+#include "Config.hpp"
+
+#include "third_party/nonstd/string_view.hpp"
#include <functional>
#include <string>
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;
// 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);
#include <algorithm>
-using Logging::log;
-
static void
delete_file(const std::string& path,
uint64_t size,
{
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
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(
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));
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);
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(
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);
}
#include "compopt.hpp"
+#include "fmtmacros.hpp"
+
#include "third_party/fmt/core.h"
// The option it too hard to handle at all.
{
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;
}
}
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;
}
}
#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 {
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(),
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
progress_receiver);
if (isatty(STDOUT_FILENO)) {
- fmt::print("\n\n");
+ PRINT_RAW(stdout, "\n\n");
}
double ratio =
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
progress_receiver);
if (isatty(STDOUT_FILENO)) {
- fmt::print("\n\n");
+ PRINT_RAW(stdout, "\n\n");
}
double old_ratio =
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);
}
#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
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(),
}
if (ret == 0) {
DWORD error = GetLastError();
- log("failed to execute {}: {} ({})",
+ LOG("failed to execute {}: {} ({})",
full_path,
Win32Util::error_message(error),
error);
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;
path = getenv("PATH");
}
if (path.empty()) {
- log("No PATH variable");
+ LOG_RAW("No PATH variable");
return {};
}
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,
}
#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.
--- /dev/null
+// 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_)
#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 {
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
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.
// 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.
// 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;
}
auto argv = args.to_argv();
- log("Executing compiler check command {}",
+ LOG("Executing compiler check command {}",
Util::format_argv_for_logging(argv.data()));
#ifdef _WIN32
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;
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;
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]);
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;
-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()
-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()
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})
--- /dev/null
+#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
--- /dev/null
+// 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;
+}
//
// 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
#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)
#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]]
#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__)
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
#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>
};
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
{};
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; };
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>
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
}
};
-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);
}
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);
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
{
} 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;
}
#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
#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
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))
#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
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)
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
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
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
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
# -------------------------------------------------------------------------
-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
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
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['
}
}
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
# 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
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[@]}"
)
}
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
}
# -------------------------------------------------------------------------
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
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
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
}
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
}
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
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
}
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
}
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"
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
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
}
- 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
test_Compression.cpp
test_Config.cpp
test_Counters.cpp
+ test_Depfile.cpp
test_FormatNonstdStringView.cpp
test_Hash.cpp
test_Lockfile.cpp
#include "../src/Util.hpp"
#include "../src/exceptions.hpp"
+#include "../src/fmtmacros.hpp"
namespace TestUtil {
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();
// 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"
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);
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;
Args args;
- SUBCASE("Non-existing file")
+ SUBCASE("Nonexistent file")
{
CHECK(Args::from_gcc_atfile("at_file") == nonstd::nullopt);
}
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");
#include "../src/Util.hpp"
#include "../src/ccache.hpp"
#include "../src/exceptions.hpp"
+#include "../src/fmtmacros.hpp"
#include "TestUtil.hpp"
#include "third_party/doctest.h"
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());
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(
" #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"
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());
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());
| 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);
}
"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"
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 = {
"(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",
--- /dev/null
+// 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();
#include "../src/Statistics.hpp"
#include "../src/Util.hpp"
+#include "../src/fmtmacros.hpp"
#include "TestUtil.hpp"
#include "third_party/doctest.h"
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);
#include "../src/Config.hpp"
#include "../src/Fd.hpp"
#include "../src/Util.hpp"
+#include "../src/fmtmacros.hpp"
#include "TestUtil.hpp"
#include "third_party/doctest.h"
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")
{
TestContext test_context;
- SUBCASE("Wipe non-existing path")
+ SUBCASE("Wipe nonexistent path")
{
CHECK_NOTHROW(Util::wipe_path("a"));
}
#include "../src/Context.hpp"
#include "../src/Statistics.hpp"
#include "../src/Util.hpp"
+#include "../src/fmtmacros.hpp"
#include "TestUtil.hpp"
#include "argprocessing.hpp"
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;
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);
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);
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);
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);
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);
{
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");
// 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"
# define CCACHE_NAME "ccache"
#endif
+using TestUtil::TestContext;
+
TEST_SUITE_BEGIN("ccache");
// Wraps find_compiler in a test friendly interface.
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
}
}
-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();